grimoire-framework 1.4.0 → 1.5.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.
@@ -7,8 +7,8 @@
7
7
  # - SHA256 hashes for change detection
8
8
  # - File types for categorization
9
9
  #
10
- version: 1.4.0
11
- generated_at: "2026-02-22T17:13:01.000Z"
10
+ version: 1.5.0
11
+ generated_at: "2026-02-22T17:38:38.314Z"
12
12
  generator: scripts/generate-install-manifest.js
13
13
  file_count: 1011
14
14
  files:
@@ -0,0 +1,319 @@
1
+ /**
2
+ * grimoire context — Live Context Manager
3
+ *
4
+ * grimoire context update Regenera CONTEXT.md a partir de todos os .jsonl
5
+ * grimoire context show Exibe CONTEXT.md no terminal
6
+ * grimoire context clean Remove arquivos context/ gerados
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const path = require('path');
12
+ const fs = require('fs');
13
+ const os = require('os');
14
+
15
+ // ── Paths ─────────────────────────────────────────────────────────────────────
16
+ function findGrimoireDir() {
17
+ const cwd = process.cwd();
18
+ const direct = path.join(cwd, '.grimoire');
19
+ const sub = path.join(cwd, 'grimoire', '.grimoire');
20
+ if (fs.existsSync(direct)) return direct;
21
+ if (fs.existsSync(sub)) return sub;
22
+ return null;
23
+ }
24
+
25
+ function getGlobalMemoryDir() {
26
+ const d = path.join(os.homedir(), '.grimoire', 'memory');
27
+ if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
28
+ return d;
29
+ }
30
+
31
+ function getContextDir(grimoireDir) {
32
+ const d = path.join(grimoireDir, 'context');
33
+ if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
34
+ return d;
35
+ }
36
+
37
+ // ── Load all memory entries ───────────────────────────────────────────────────
38
+ function loadAllEntries(grimoireDir, includeGlobal = true) {
39
+ const entries = [];
40
+
41
+ // Local sessions
42
+ const sessionsDir = path.join(grimoireDir, 'memory', 'sessions');
43
+ if (fs.existsSync(sessionsDir)) {
44
+ for (const f of fs.readdirSync(sessionsDir).filter(f => f.endsWith('.jsonl')).sort()) {
45
+ const raw = fs.readFileSync(path.join(sessionsDir, f), 'utf8');
46
+ for (const line of raw.split('\n').filter(l => l.trim())) {
47
+ try { entries.push({ ...JSON.parse(line), date: f.slice(0, 10), source: 'local' }); } catch (_) { }
48
+ }
49
+ }
50
+ }
51
+
52
+ // Pinned (local)
53
+ const pinnedFile = path.join(grimoireDir, 'memory', 'pinned.jsonl');
54
+ if (fs.existsSync(pinnedFile)) {
55
+ const raw = fs.readFileSync(pinnedFile, 'utf8');
56
+ for (const line of raw.split('\n').filter(l => l.trim())) {
57
+ try { entries.push({ ...JSON.parse(line), source: 'pinned' }); } catch (_) { }
58
+ }
59
+ }
60
+
61
+ // Global
62
+ if (includeGlobal) {
63
+ const globalFile = path.join(getGlobalMemoryDir(), 'global.jsonl');
64
+ if (fs.existsSync(globalFile)) {
65
+ const raw = fs.readFileSync(globalFile, 'utf8');
66
+ for (const line of raw.split('\n').filter(l => l.trim())) {
67
+ try { entries.push({ ...JSON.parse(line), source: 'global' }); } catch (_) { }
68
+ }
69
+ }
70
+ }
71
+
72
+ return entries;
73
+ }
74
+
75
+ // ── Build Markdown files ──────────────────────────────────────────────────────
76
+ function buildContextMd(entries) {
77
+ const now = new Date().toISOString().split('T')[0];
78
+ const pinned = entries.filter(e => e.source === 'pinned');
79
+ const recent = entries.filter(e => e.source !== 'pinned').slice(-20).reverse();
80
+ const globals = entries.filter(e => e.source === 'global').slice(-10).reverse();
81
+
82
+ const lines = [];
83
+ lines.push(`# 🧠 Contexto do Projeto\n`);
84
+ lines.push(`> Auto-gerado em ${now} via \`grimoire context update\`\n`);
85
+
86
+ if (pinned.length) {
87
+ lines.push(`## 📌 Fixadas\n`);
88
+ pinned.forEach(e => lines.push(`- ${e.content}`));
89
+ lines.push('');
90
+ }
91
+
92
+ const decisions = recent.filter(e => e.tag === 'decisão' || e.tag === 'decisao' || e.tag === 'decision');
93
+ if (decisions.length) {
94
+ lines.push(`## ✅ Últimas Decisões\n`);
95
+ decisions.slice(0, 8).forEach(e => lines.push(`- **${e.date || ''}** ${e.content}`));
96
+ lines.push('');
97
+ }
98
+
99
+ const patterns = recent.filter(e => e.tag === 'padrão' || e.tag === 'padrao' || e.tag === 'pattern');
100
+ if (patterns.length) {
101
+ lines.push(`## 🔧 Padrões Estabelecidos\n`);
102
+ patterns.slice(0, 8).forEach(e => lines.push(`- **${e.date || ''}** ${e.content}`));
103
+ lines.push('');
104
+ }
105
+
106
+ const other = recent.filter(e => !['decisão', 'decisao', 'decision', 'padrão', 'padrao', 'pattern'].includes(e.tag));
107
+ if (other.length) {
108
+ lines.push(`## 📝 Entradas Recentes\n`);
109
+ other.slice(0, 10).forEach(e => {
110
+ const tag = e.tag ? ` \`#${e.tag}\`` : '';
111
+ const src = e.source === 'global' ? ' 🌐' : '';
112
+ lines.push(`- **${e.date || ''}** ${e.content}${tag}${src}`);
113
+ });
114
+ lines.push('');
115
+ }
116
+
117
+ if (globals.length) {
118
+ lines.push(`## 🌐 Memória Global (cross-projeto)\n`);
119
+ globals.slice(0, 5).forEach(e => {
120
+ const tag = e.tag ? ` \`#${e.tag}\`` : '';
121
+ lines.push(`- ${e.content}${tag}`);
122
+ });
123
+ lines.push('');
124
+ }
125
+
126
+ return lines.join('\n');
127
+ }
128
+
129
+ function buildTagMd(entries, tag, title, emoji) {
130
+ const variants = [tag, tag.normalize('NFD').replace(/[\u0300-\u036f]/g, '')];
131
+ const filtered = entries.filter(e => variants.includes(e.tag)).slice(-30).reverse();
132
+ if (!filtered.length) return null;
133
+
134
+ const lines = [`# ${emoji} ${title}\n`, `> Auto-gerado pelo Grimoire\n`];
135
+ filtered.forEach(e => {
136
+ const src = e.source === 'global' ? ' 🌐' : '';
137
+ lines.push(`- **${e.date || ''}** ${e.content}${src}`);
138
+ });
139
+ return lines.join('\n');
140
+ }
141
+
142
+ // ── Inject into GEMINI.md ─────────────────────────────────────────────────────
143
+ const MARKER_START = '<!-- grimoire-MANAGED-START: memory-context -->';
144
+ const MARKER_END = '<!-- grimoire-MANAGED-END: memory-context -->';
145
+
146
+ function buildGeminiBlock(entries) {
147
+ const now = new Date().toISOString().split('T')[0];
148
+ const pinned = entries.filter(e => e.source === 'pinned');
149
+ const recent = entries.filter(e => e.source !== 'pinned').slice(-15).reverse();
150
+ const decisions = recent.filter(e => ['decisão', 'decisao', 'decision'].includes(e.tag)).slice(0, 5);
151
+ const patterns = recent.filter(e => ['padrão', 'padrao', 'pattern'].includes(e.tag)).slice(0, 5);
152
+
153
+ const block = [];
154
+ block.push(MARKER_START);
155
+ block.push(`## 🧠 Contexto do Projeto (auto-gerado)\n`);
156
+
157
+ if (pinned.length) {
158
+ block.push(`### 📌 Fixadas\n`);
159
+ pinned.forEach(e => block.push(`- ${e.content}`));
160
+ block.push('');
161
+ }
162
+
163
+ if (decisions.length) {
164
+ block.push(`### ✅ Últimas decisões\n`);
165
+ decisions.forEach(e => block.push(`- ${e.date}: ${e.content}`));
166
+ block.push('');
167
+ }
168
+
169
+ if (patterns.length) {
170
+ block.push(`### 🔧 Padrões\n`);
171
+ patterns.forEach(e => block.push(`- ${e.date}: ${e.content}`));
172
+ block.push('');
173
+ }
174
+
175
+ // Any other recent entries without special tag
176
+ const others = recent.filter(e => !['decisão', 'decisao', 'decision', 'padrão', 'padrao', 'pattern'].includes(e.tag)).slice(0, 5);
177
+ if (others.length) {
178
+ block.push(`### 📝 Recentes\n`);
179
+ others.forEach(e => {
180
+ const tag = e.tag ? ` [#${e.tag}]` : '';
181
+ block.push(`- ${e.date}: ${e.content}${tag}`);
182
+ });
183
+ block.push('');
184
+ }
185
+
186
+ block.push(`> _Atualizado em ${now}. Use \`grimoire context update\` para regenerar._`);
187
+ block.push(MARKER_END);
188
+ return block.join('\n');
189
+ }
190
+
191
+ function injectIntoGeminiMd(grimoireDir, entries) {
192
+ // GEMINI.md can be at project root or parent
193
+ const cwd = process.cwd();
194
+ const candidates = [
195
+ path.join(cwd, 'GEMINI.md'),
196
+ path.join(path.dirname(grimoireDir), 'GEMINI.md'),
197
+ ];
198
+ const geminiPath = candidates.find(p => fs.existsSync(p));
199
+ if (!geminiPath) return false;
200
+
201
+ const original = fs.readFileSync(geminiPath, 'utf8');
202
+ const newBlock = buildGeminiBlock(entries);
203
+
204
+ let updated;
205
+ if (original.includes(MARKER_START)) {
206
+ // Replace existing block
207
+ const re = new RegExp(`${regEscape(MARKER_START)}[\\s\\S]*?${regEscape(MARKER_END)}`, 'g');
208
+ updated = original.replace(re, newBlock);
209
+ } else {
210
+ // Append before last ---
211
+ const lastHr = original.lastIndexOf('\n---');
212
+ if (lastHr !== -1) {
213
+ updated = original.slice(0, lastHr) + '\n\n' + newBlock + '\n' + original.slice(lastHr);
214
+ } else {
215
+ updated = original + '\n\n' + newBlock + '\n';
216
+ }
217
+ }
218
+
219
+ if (updated !== original) {
220
+ fs.writeFileSync(geminiPath, updated, 'utf8');
221
+ return true;
222
+ }
223
+ return false;
224
+ }
225
+
226
+ function regEscape(s) {
227
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
228
+ }
229
+
230
+ // ── Core update logic ─────────────────────────────────────────────────────────
231
+ function doUpdate(grimoireDir, silent = false) {
232
+ const contextDir = getContextDir(grimoireDir);
233
+ const entries = loadAllEntries(grimoireDir, true);
234
+
235
+ // CONTEXT.md
236
+ fs.writeFileSync(path.join(contextDir, 'CONTEXT.md'), buildContextMd(entries), 'utf8');
237
+
238
+ // decisions.md
239
+ const dec = buildTagMd(entries, 'decisão', 'Decisões', '✅') ||
240
+ buildTagMd(entries, 'decisao', 'Decisões', '✅') ||
241
+ buildTagMd(entries, 'decision', 'Decisões', '✅');
242
+ if (dec) fs.writeFileSync(path.join(contextDir, 'decisions.md'), dec, 'utf8');
243
+
244
+ // patterns.md
245
+ const pat = buildTagMd(entries, 'padrão', 'Padrões', '🔧') ||
246
+ buildTagMd(entries, 'padrao', 'Padrões', '🔧') ||
247
+ buildTagMd(entries, 'pattern', 'Padrões', '🔧');
248
+ if (pat) fs.writeFileSync(path.join(contextDir, 'patterns.md'), pat, 'utf8');
249
+
250
+ // pinned.md
251
+ const pinned = entries.filter(e => e.source === 'pinned');
252
+ if (pinned.length) {
253
+ const pinnedLines = ['# 📌 Memórias Fixadas\n', '> Estas entradas são sempre incluídas no contexto dos agentes.\n'];
254
+ pinned.forEach(e => pinnedLines.push(`- ${e.content}`));
255
+ fs.writeFileSync(path.join(contextDir, 'pinned.md'), pinnedLines.join('\n'), 'utf8');
256
+ }
257
+
258
+ // Inject GEMINI.md
259
+ const injected = injectIntoGeminiMd(grimoireDir, entries);
260
+
261
+ if (!silent) {
262
+ console.log('\n✅ Contexto atualizado!\n');
263
+ console.log(` 📄 .grimoire/context/CONTEXT.md`);
264
+ if (dec) console.log(` ✅ .grimoire/context/decisions.md`);
265
+ if (pat) console.log(` 🔧 .grimoire/context/patterns.md`);
266
+ if (pinned.length) console.log(` 📌 .grimoire/context/pinned.md`);
267
+ if (injected) console.log(` 🔮 GEMINI.md atualizado com bloco memory-context`);
268
+ console.log(`\n ${entries.length} entradas indexadas`);
269
+ console.log(` grimoire context show ← ver o resultado\n`);
270
+ }
271
+
272
+ return { entries, injected };
273
+ }
274
+
275
+ // ── run ───────────────────────────────────────────────────────────────────────
276
+ function run(args) {
277
+ const sub = args[0] || 'update';
278
+
279
+ const grimoireDir = findGrimoireDir();
280
+ if (!grimoireDir) {
281
+ console.error('❌ .grimoire/ not found. Run: npx grimoire-framework install');
282
+ return;
283
+ }
284
+
285
+ switch (sub) {
286
+ case 'update':
287
+ doUpdate(grimoireDir);
288
+ break;
289
+
290
+ case 'show': {
291
+ const contextFile = path.join(grimoireDir, 'context', 'CONTEXT.md');
292
+ if (!fs.existsSync(contextFile)) {
293
+ console.log('\n⚠️ Contexto ainda não gerado. Rode: grimoire context update\n');
294
+ return;
295
+ }
296
+ console.log('\n' + fs.readFileSync(contextFile, 'utf8'));
297
+ break;
298
+ }
299
+
300
+ case 'clean': {
301
+ const contextDir = path.join(grimoireDir, 'context');
302
+ if (fs.existsSync(contextDir)) {
303
+ fs.readdirSync(contextDir).forEach(f => fs.unlinkSync(path.join(contextDir, f)));
304
+ console.log('\n✅ Arquivos de contexto removidos (.jsonl preservados)\n');
305
+ } else {
306
+ console.log('\nℹ️ Nada para limpar.\n');
307
+ }
308
+ break;
309
+ }
310
+
311
+ default:
312
+ console.log('\nUsage:\n');
313
+ console.log(' grimoire context update Regenera CONTEXT.md + injeta GEMINI.md');
314
+ console.log(' grimoire context show Exibe CONTEXT.md');
315
+ console.log(' grimoire context clean Remove arquivos gerados\n');
316
+ }
317
+ }
318
+
319
+ module.exports = { run, doUpdate, loadAllEntries, getGlobalMemoryDir };
@@ -23,7 +23,8 @@ async function run(args) {
23
23
  baseDir = path.join(baseDir, 'grimoire');
24
24
  }
25
25
 
26
- const memoryDir = path.join(baseDir, '.grimoire', 'memory');
26
+ const grimoireDir = path.join(baseDir, '.grimoire');
27
+ const memoryDir = path.join(grimoireDir, 'memory');
27
28
 
28
29
  if (!existsSync(memoryDir)) {
29
30
  console.error('❌ Memory system not found. Run "grimoire install" first.');
@@ -33,7 +34,10 @@ async function run(args) {
33
34
  try {
34
35
  switch (subCommand) {
35
36
  case 'save':
36
- await saveMemory(args.slice(1), memoryDir);
37
+ await saveMemory(args.slice(1), memoryDir, grimoireDir);
38
+ break;
39
+ case 'pin':
40
+ await pinMemory(args.slice(1), memoryDir, grimoireDir);
37
41
  break;
38
42
  case 'list-tags':
39
43
  await listTags(memoryDir);
@@ -66,10 +70,18 @@ async function run(args) {
66
70
  }
67
71
  }
68
72
 
73
+ // ── Auto-update context (silent) ────────────────────────────────────────────
74
+ function silentContextUpdate(grimoireDir) {
75
+ try {
76
+ const { doUpdate } = require('./context');
77
+ doUpdate(grimoireDir, true); // silent = true
78
+ } catch (_) { } // never block memory save
79
+ }
80
+
69
81
  /**
70
82
  * Saves memory using JSONL append (O(1) complexity)
71
83
  */
72
- async function saveMemory(args, memoryDir) {
84
+ async function saveMemory(args, memoryDir, grimoireDir) {
73
85
  // Extract --tag value if present
74
86
  const tagIdx = args.findIndex(a => a === '--tag' || a.startsWith('--tag='));
75
87
  let tag = null;
@@ -98,6 +110,10 @@ async function saveMemory(args, memoryDir) {
98
110
  }
99
111
  }
100
112
 
113
+ // Extract --global flag
114
+ const isGlobal = filteredArgs.includes('--global');
115
+ filteredArgs = filteredArgs.filter(a => a !== '--global');
116
+
101
117
  const content = filteredArgs.join(' ');
102
118
  if (!content) {
103
119
  console.error('❌ Please provide content to save.');
@@ -105,8 +121,6 @@ async function saveMemory(args, memoryDir) {
105
121
  }
106
122
 
107
123
  const today = new Date().toISOString().split('T')[0];
108
- const sessionFile = path.join(memoryDir, 'sessions', `${today}.jsonl`);
109
-
110
124
  const entry = {
111
125
  timestamp: new Date().toISOString(),
112
126
  content: content,
@@ -114,6 +128,21 @@ async function saveMemory(args, memoryDir) {
114
128
  ...(storyId ? { story: storyId } : {}),
115
129
  };
116
130
 
131
+ if (isGlobal) {
132
+ // Save to global memory
133
+ const { getGlobalMemoryDir } = require('./context');
134
+ const globalDir = getGlobalMemoryDir();
135
+ const globalFile = require('path').join(globalDir, 'global.jsonl');
136
+ const fss = require('fs');
137
+ fss.appendFileSync(globalFile, JSON.stringify({ ...entry, source: 'global' }) + '\n', 'utf8');
138
+ const tagLabel = tag ? ` [#${tag}]` : '';
139
+ console.log(`✅ Memory saved globally 🌐${tagLabel}`);
140
+ silentContextUpdate(grimoireDir);
141
+ return;
142
+ }
143
+
144
+ const sessionFile = require('path').join(memoryDir, 'sessions', `${today}.jsonl`);
145
+
117
146
  // Ensure file exists for lockfile
118
147
  if (!existsSync(sessionFile)) {
119
148
  await fs.writeFile(sessionFile, '', 'utf8');
@@ -134,7 +163,10 @@ async function saveMemory(args, memoryDir) {
134
163
  // Append entry as a new line in JSONL format
135
164
  await fs.appendFile(sessionFile, JSON.stringify(entry) + '\n', 'utf8');
136
165
  const tagLabel = tag ? ` [#${tag}]` : '';
137
- console.log(`✅ Memory appended to session ${today}${tagLabel}`);
166
+ const storyLabel = storyId ? ` [${storyId}]` : '';
167
+ console.log(`✅ Memory saved${tagLabel}${storyLabel}`);
168
+ // Auto-update .grimoire/context/ silently
169
+ silentContextUpdate(grimoireDir);
138
170
  } catch (err) {
139
171
  console.error(`❌ Failed to acquire lock for memory file: ${err.message}`);
140
172
  } finally {
@@ -142,6 +174,22 @@ async function saveMemory(args, memoryDir) {
142
174
  }
143
175
  }
144
176
 
177
+ /**
178
+ * Pins a memory entry permanently in pinned.jsonl
179
+ */
180
+ async function pinMemory(args, memoryDir, grimoireDir) {
181
+ const content = args.filter(a => !a.startsWith('-')).join(' ');
182
+ if (!content) {
183
+ console.log('Usage: grimoire memory pin "texto"\n');
184
+ return;
185
+ }
186
+ const pinnedFile = require('path').join(memoryDir, 'pinned.jsonl');
187
+ const entry = { timestamp: new Date().toISOString(), content, source: 'pinned' };
188
+ require('fs').appendFileSync(pinnedFile, JSON.stringify(entry) + '\n', 'utf8');
189
+ console.log(`📌 Memoria fixada: "${content}"`);
190
+ silentContextUpdate(grimoireDir);
191
+ }
192
+
145
193
  /**
146
194
  * Lists sessions scanning the directory
147
195
  */
@@ -1,13 +1,11 @@
1
1
  /**
2
- * grimoire pro — License & Feature Gate System
2
+ * grimoire pro — Feature Catalogue
3
3
  *
4
- * Phase 1: Offline-first with HMAC key validation
5
- * Phase 2: Online validation via Supabase Edge Function (future)
4
+ * Todos os recursos Pro estão disponíveis por padrão.
5
+ * O sistema de licença foi removido sem chave, sem bloqueio.
6
6
  *
7
- * grimoire pro activate <key> Ativa a licença Pro
8
- * grimoire pro status Mostra status da licença
9
- * grimoire pro features Lista features Pro disponíveis
10
- * grimoire pro deactivate Remove a licença
7
+ * grimoire pro status Mostra status e features
8
+ * grimoire pro features Lista todas as features
11
9
  */
12
10
 
13
11
  'use strict';
@@ -69,17 +67,19 @@ function isLicenseValid(license) {
69
67
  return license.checksum === expected;
70
68
  }
71
69
 
72
- // ── Public API for other modules ───────────────────────────────────────────────
70
+ // ── Public API (all features unlocked by default) ────────────────────────────
71
+ /**
72
+ * Always returns true — all Pro features are available without a license key.
73
+ */
73
74
  function isPro() {
74
- const license = loadLicense();
75
- return isLicenseValid(license) && license.type === 'pro';
75
+ return true;
76
76
  }
77
77
 
78
+ /**
79
+ * Always returns true — every feature ID is available.
80
+ */
78
81
  function hasFeature(featureId) {
79
- const license = loadLicense();
80
- if (!isLicenseValid(license)) return false;
81
- if (license.type === 'pro') return PRO_FEATURES.some(f => f.id === featureId);
82
- return license.features?.includes(featureId) || false;
82
+ return true;
83
83
  }
84
84
 
85
85
  // ── readline prompt ────────────────────────────────────────────────────────────
@@ -93,145 +93,43 @@ function prompt(q) {
93
93
  async function run(args) {
94
94
  const sub = args[0];
95
95
  switch (sub) {
96
- case 'activate': await activate(args[1]); break;
97
- case 'deactivate': deactivate(); break;
96
+ case 'activate': showAlreadyOpen(); break;
97
+ case 'deactivate': showAlreadyOpen(); break;
98
98
  case 'features': showFeatures(); break;
99
99
  case 'status':
100
100
  default: showStatus(); break;
101
101
  }
102
102
  }
103
103
 
104
- // ── Activate ───────────────────────────────────────────────────────────────────
105
- async function activate(keyArg) {
106
- console.log('\n🔐 Grimoire Pro Ativação de Licença\n');
107
-
108
- const key = keyArg || await prompt('Chave de licença (PRO-XXXX-XXXX-XXXX-XXXX): ');
109
-
110
- if (!validateKeyFormat(key)) {
111
- console.error('❌ Formato inválido. Use: PRO-XXXX-XXXX-XXXX-XXXX');
112
- console.log(' Obtenha sua chave em: https://grimoire.dev/pro\n');
113
- return;
114
- }
115
-
116
- const email = await prompt('Email associado à licença: ');
117
- if (!email || !email.includes('@')) {
118
- console.error('❌ Email inválido.');
119
- return;
120
- }
121
-
122
- // Offline HMAC validation
123
- const checksum = generateKeyChecksum(email, key);
124
-
125
- // Try online validation (Phase 2 ready but not blocking)
126
- let onlineValidated = false;
127
- try {
128
- const https = require('https');
129
- const result = await new Promise((resolve, reject) => {
130
- const data = JSON.stringify({ key, email });
131
- const req = https.request({
132
- hostname: 'api.grimoire.dev',
133
- path: '/v1/license/validate',
134
- method: 'POST',
135
- headers: { 'Content-Type': 'application/json', 'Content-Length': data.length },
136
- timeout: 4000,
137
- }, (res) => {
138
- let body = '';
139
- res.on('data', d => body += d);
140
- res.on('end', () => { try { resolve(JSON.parse(body)); } catch (e) { reject(e); } });
141
- });
142
- req.on('error', reject);
143
- req.on('timeout', () => req.destroy());
144
- req.write(data);
145
- req.end();
146
- });
147
- onlineValidated = result.valid === true;
148
- } catch (_) {
149
- // Offline — use local validation only
150
- }
151
-
152
- const license = {
153
- type: 'pro',
154
- key,
155
- email,
156
- checksum,
157
- activatedAt: new Date().toISOString(),
158
- expiresAt: null, // null = never (or set by server in Phase 2)
159
- onlineValidated,
160
- features: PRO_FEATURES.map(f => f.id),
161
- };
162
-
163
- saveLicense(license);
164
-
165
- console.log(`
166
- ✅ Grimoire Pro ativado!
167
-
168
- Email: ${email}
169
- Chave: ${key.substring(0, 7)}...${key.slice(-4)}
170
- Status: ${onlineValidated ? '✅ Validado online' : '✅ Ativado (validação offline)'}
171
-
172
- 🎯 Features ativas:`);
173
- PRO_FEATURES.forEach(f => console.log(` ✅ ${f.name} — ${f.desc}`));
174
- console.log('\n💡 Use "grimoire pro status" para ver os detalhes\n');
104
+ // ── showAlreadyOpen ───────────────────────────────────────────────────────────
105
+ function showAlreadyOpen() {
106
+ console.log('\n🔓 Grimoire Pro está aberto!');
107
+ console.log(' Todos os recursos já estão disponíveis sem preciso de chave.\n');
108
+ showFeatures();
175
109
  }
176
110
 
177
111
  // ── Status ─────────────────────────────────────────────────────────────────────
178
112
  function showStatus() {
179
- const license = loadLicense();
180
113
  const sep = '─'.repeat(50);
181
-
182
- console.log(`\n🔐 Grimoire Pro Status da Licença\n${sep}`);
183
-
184
- if (!license) {
185
- console.log(` Status: ⭕ Não ativado (Community)\n`);
186
- console.log(` ${sep}`);
187
- console.log(` Para ativar: grimoire pro activate <chave>`);
188
- console.log(` Obter licença: https://grimoire.dev/pro\n`);
189
- showFeatureList(false);
190
- return;
191
- }
192
-
193
- const valid = isLicenseValid(license);
194
- const isPro = license.type === 'pro' && valid;
195
- const expires = license.expiresAt ? new Date(license.expiresAt).toLocaleDateString('pt-BR') : 'Sem expiração';
196
-
197
- console.log(` Status: ${valid ? (isPro ? '✅ Pro Ativo' : '✅ Community') : '❌ Licença inválida/expirada'}`);
198
- if (license.email) console.log(` Email: ${license.email}`);
199
- if (license.key) console.log(` Chave: ${license.key.substring(0, 7)}...${license.key.slice(-4)}`);
200
- console.log(` Ativado: ${license.activatedAt ? new Date(license.activatedAt).toLocaleDateString('pt-BR') : 'N/A'}`);
201
- console.log(` Expira: ${expires}`);
114
+ console.log(`\n🔓 Grimoire Pro — Status\n${sep}`);
115
+ console.log(` Status: ✅ Aberto (todos os recursos disponíveis)`);
116
+ console.log(` Licença: Sem necessidade de chave`);
117
+ console.log(` Features: ${PRO_FEATURES.length} recursos ativos`);
202
118
  console.log(` ${sep}`);
203
-
204
- showFeatureList(isPro);
119
+ showFeatureList(true);
205
120
  }
206
121
 
207
122
  // ── Features ───────────────────────────────────────────────────────────────────
208
123
  function showFeatures() {
209
- const license = loadLicense();
210
- const active = license && isLicenseValid(license) && license.type === 'pro';
211
- console.log('\n🎯 Grimoire Pro — Features\n');
212
- showFeatureList(active);
124
+ console.log('\n🎯 Grimoire Pro — Features (todas ativas)\n');
125
+ showFeatureList(true);
213
126
  }
214
127
 
215
128
  function showFeatureList(active) {
216
129
  PRO_FEATURES.forEach(f => {
217
- const status = active ? '' : '🔒';
218
- console.log(` ${status} ${f.name}`);
130
+ console.log(` ${f.name}`);
219
131
  console.log(` ${f.desc}\n`);
220
132
  });
221
- if (!active) {
222
- console.log(` 🔗 Ative em: https://grimoire.dev/pro`);
223
- console.log(` grimoire pro activate\n`);
224
- }
225
- }
226
-
227
- // ── Deactivate ─────────────────────────────────────────────────────────────────
228
- function deactivate() {
229
- if (!fs.existsSync(LICENSE_FILE)) {
230
- console.log('ℹ️ Nenhuma licença ativa encontrada.');
231
- return;
232
- }
233
- fs.unlinkSync(LICENSE_FILE);
234
- console.log('✅ Licença removida. Grimoire voltou ao modo Community.');
235
133
  }
236
134
 
237
135
  module.exports = { run, isPro, hasFeature };
@@ -151,6 +151,9 @@ async function main() {
151
151
  case 'export':
152
152
  require('./commands/exportall').run(args.slice(1));
153
153
  break;
154
+ case 'context':
155
+ require('./commands/context').run(args.slice(1));
156
+ break;
154
157
  case 'whoami':
155
158
  handleWhoami();
156
159
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grimoire-framework",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Grimoire: AI-Orchestrated System for Full Stack Development - Core Framework",
5
5
  "publishConfig": {
6
6
  "access": "public"