@hbarefoot/engram 1.1.0 → 1.2.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/bin/engram.js +275 -90
- package/dashboard/dist/assets/index-D0xT6oKC.css +1 -0
- package/dashboard/dist/assets/index-D3bysGhj.js +45 -0
- package/dashboard/dist/engram-logo.png +0 -0
- package/dashboard/dist/favicon.png +0 -0
- package/dashboard/dist/index.html +3 -3
- package/package.json +9 -2
- package/src/embed/index.js +116 -16
- package/src/import/index.js +259 -0
- package/src/import/parsers/claude.js +208 -0
- package/src/import/parsers/cursorrules.js +153 -0
- package/src/import/parsers/env.js +129 -0
- package/src/import/parsers/git.js +194 -0
- package/src/import/parsers/obsidian.js +219 -0
- package/src/import/parsers/package.js +177 -0
- package/src/import/parsers/shell.js +256 -0
- package/src/import/parsers/ssh.js +132 -0
- package/src/import/wizard.js +280 -0
- package/src/server/mcp.js +5 -1
- package/src/server/rest.js +137 -9
- package/src/utils/format.js +224 -0
- package/dashboard/dist/assets/index-BHkLa5w_.css +0 -1
- package/dashboard/dist/assets/index-D9QR_Cnu.js +0 -45
package/bin/engram.js
CHANGED
|
@@ -16,19 +16,35 @@ import { fileURLToPath } from 'url';
|
|
|
16
16
|
import { dirname, join } from 'path';
|
|
17
17
|
import fs from 'fs';
|
|
18
18
|
|
|
19
|
-
// Read version from package.json
|
|
19
|
+
// Read version from package.json (may not exist when running as bundled sidecar)
|
|
20
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
21
21
|
const __dirname = dirname(__filename);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
let version = '1.1.0';
|
|
23
|
+
try {
|
|
24
|
+
const packageJson = JSON.parse(
|
|
25
|
+
readFileSync(join(__dirname, '../package.json'), 'utf-8')
|
|
26
|
+
);
|
|
27
|
+
version = packageJson.version;
|
|
28
|
+
} catch {
|
|
29
|
+
// Running as bundled sidecar — package.json not available
|
|
30
|
+
}
|
|
25
31
|
|
|
26
32
|
const program = new Command();
|
|
27
33
|
|
|
28
34
|
program
|
|
29
35
|
.name('engram')
|
|
30
36
|
.description('Persistent memory for AI agents - SQLite for agent state')
|
|
31
|
-
.version(
|
|
37
|
+
.version(version);
|
|
38
|
+
|
|
39
|
+
// ── Lazy format helpers (only loaded for non-MCP commands) ───────────
|
|
40
|
+
|
|
41
|
+
let fmt;
|
|
42
|
+
async function loadFormat() {
|
|
43
|
+
if (!fmt) {
|
|
44
|
+
fmt = await import('../src/utils/format.js');
|
|
45
|
+
}
|
|
46
|
+
return fmt;
|
|
47
|
+
}
|
|
32
48
|
|
|
33
49
|
// Start server command
|
|
34
50
|
program
|
|
@@ -39,28 +55,32 @@ program
|
|
|
39
55
|
.option('--config <path>', 'Path to config file')
|
|
40
56
|
.action(async (options) => {
|
|
41
57
|
if (options.mcpOnly) {
|
|
42
|
-
// Start MCP server only (stdio mode)
|
|
58
|
+
// Start MCP server only (stdio mode) — no formatting imports
|
|
43
59
|
logger.info('Starting Engram MCP server (stdio mode)...');
|
|
44
60
|
await startMCPServer(options.config);
|
|
45
61
|
} else {
|
|
46
|
-
|
|
62
|
+
const f = await loadFormat();
|
|
63
|
+
const chalk = (await import('chalk')).default;
|
|
47
64
|
const config = loadConfig(options.config);
|
|
48
65
|
const port = parseInt(options.port);
|
|
49
66
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
f.printHeader(version);
|
|
68
|
+
|
|
69
|
+
f.info(`REST API ${chalk.cyan(`http://localhost:${port}`)}`);
|
|
70
|
+
f.info(`Dashboard ${chalk.cyan(`http://localhost:${port}`)}`);
|
|
71
|
+
console.log('');
|
|
53
72
|
|
|
54
73
|
await startRESTServer(config, port);
|
|
55
74
|
|
|
56
75
|
// Keep process alive
|
|
57
76
|
process.on('SIGINT', () => {
|
|
58
|
-
|
|
77
|
+
console.log('');
|
|
78
|
+
f.info('Shutting down...');
|
|
59
79
|
process.exit(0);
|
|
60
80
|
});
|
|
61
81
|
|
|
62
82
|
process.on('SIGTERM', () => {
|
|
63
|
-
|
|
83
|
+
f.info('Shutting down...');
|
|
64
84
|
process.exit(0);
|
|
65
85
|
});
|
|
66
86
|
}
|
|
@@ -76,6 +96,7 @@ program
|
|
|
76
96
|
.option('-n, --namespace <name>', 'Project/scope namespace', 'default')
|
|
77
97
|
.option('--config <path>', 'Path to config file')
|
|
78
98
|
.action(async (content, options) => {
|
|
99
|
+
const f = await loadFormat();
|
|
79
100
|
try {
|
|
80
101
|
const config = loadConfig(options.config);
|
|
81
102
|
const db = initDatabase(getDatabasePath(config));
|
|
@@ -84,7 +105,7 @@ program
|
|
|
84
105
|
const validation = validateContent(content, { autoRedact: config.security?.secretDetection !== false });
|
|
85
106
|
|
|
86
107
|
if (!validation.valid) {
|
|
87
|
-
|
|
108
|
+
f.error(`Cannot store memory: ${validation.errors.join(', ')}`);
|
|
88
109
|
process.exit(1);
|
|
89
110
|
}
|
|
90
111
|
|
|
@@ -103,31 +124,40 @@ program
|
|
|
103
124
|
memoryData.entity = extracted.entity;
|
|
104
125
|
}
|
|
105
126
|
|
|
106
|
-
// Generate embedding
|
|
127
|
+
// Generate embedding with spinner
|
|
128
|
+
const spin = f.spinner('Generating embedding...');
|
|
129
|
+
spin.start();
|
|
107
130
|
try {
|
|
108
131
|
const { generateEmbedding } = await import('../src/embed/index.js');
|
|
109
132
|
const embedding = await generateEmbedding(validation.content, getModelsPath(config));
|
|
110
133
|
memoryData.embedding = embedding;
|
|
134
|
+
spin.succeed('Embedding generated');
|
|
111
135
|
} catch (error) {
|
|
112
|
-
|
|
136
|
+
spin.warn('Embedding skipped (model unavailable)');
|
|
113
137
|
}
|
|
114
138
|
|
|
115
139
|
const memory = createMemory(db, memoryData);
|
|
116
140
|
|
|
117
|
-
console.log('
|
|
118
|
-
|
|
119
|
-
console.log(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
141
|
+
console.log('');
|
|
142
|
+
f.success('Memory stored');
|
|
143
|
+
console.log('');
|
|
144
|
+
f.printKeyValue([
|
|
145
|
+
['ID', memory.id],
|
|
146
|
+
['Category', f.categoryBadge(memory.category)],
|
|
147
|
+
['Entity', memory.entity || 'none'],
|
|
148
|
+
['Confidence', f.confidenceColor(memory.confidence)],
|
|
149
|
+
['Namespace', memory.namespace]
|
|
150
|
+
]);
|
|
123
151
|
|
|
124
152
|
if (validation.warnings?.length > 0) {
|
|
125
|
-
console.log(
|
|
153
|
+
console.log('');
|
|
154
|
+
f.warning(`Warnings: ${validation.warnings.join(', ')}`);
|
|
126
155
|
}
|
|
156
|
+
console.log('');
|
|
127
157
|
|
|
128
158
|
db.close();
|
|
129
159
|
} catch (error) {
|
|
130
|
-
|
|
160
|
+
f.error(error.message);
|
|
131
161
|
process.exit(1);
|
|
132
162
|
}
|
|
133
163
|
});
|
|
@@ -136,12 +166,14 @@ program
|
|
|
136
166
|
program
|
|
137
167
|
.command('recall <query>')
|
|
138
168
|
.description('Recall memories matching a query')
|
|
139
|
-
.option('-l, --limit <n>', 'Max results (default 5)', parseInt, 5)
|
|
169
|
+
.option('-l, --limit <n>', 'Max results (default 5)', v => parseInt(v, 10), 5)
|
|
140
170
|
.option('-c, --category <type>', 'Filter by category')
|
|
141
171
|
.option('-n, --namespace <name>', 'Filter by namespace')
|
|
142
172
|
.option('--threshold <score>', 'Minimum relevance score (0-1)', parseFloat, 0.3)
|
|
143
173
|
.option('--config <path>', 'Path to config file')
|
|
144
174
|
.action(async (query, options) => {
|
|
175
|
+
const f = await loadFormat();
|
|
176
|
+
const chalk = (await import('chalk')).default;
|
|
145
177
|
try {
|
|
146
178
|
const config = loadConfig(options.config);
|
|
147
179
|
const db = initDatabase(getDatabasePath(config));
|
|
@@ -159,12 +191,42 @@ program
|
|
|
159
191
|
modelsPath
|
|
160
192
|
);
|
|
161
193
|
|
|
162
|
-
|
|
163
|
-
|
|
194
|
+
if (memories.length === 0) {
|
|
195
|
+
console.log('');
|
|
196
|
+
f.info('No relevant memories found.');
|
|
197
|
+
console.log('');
|
|
198
|
+
db.close();
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
f.printSection(`${memories.length} result${memories.length === 1 ? '' : 's'}`);
|
|
203
|
+
console.log('');
|
|
204
|
+
|
|
205
|
+
memories.forEach((memory, index) => {
|
|
206
|
+
const num = chalk.bold(`#${index + 1}`);
|
|
207
|
+
const cat = f.categoryBadge(memory.category);
|
|
208
|
+
const id = f.shortId(memory.id);
|
|
209
|
+
const score = memory.score ? f.scoreDisplay(memory.score, { showBar: true }) : '';
|
|
210
|
+
|
|
211
|
+
console.log(`${num} ${cat} ${id} ${score}`);
|
|
212
|
+
console.log(` ${memory.content}`);
|
|
213
|
+
|
|
214
|
+
if (memory.scoreBreakdown) {
|
|
215
|
+
const bd = memory.scoreBreakdown;
|
|
216
|
+
const parts = [
|
|
217
|
+
`sim=${chalk.dim(bd.similarity.toFixed(2))}`,
|
|
218
|
+
`rec=${chalk.dim(bd.recency.toFixed(2))}`,
|
|
219
|
+
`conf=${chalk.dim(bd.confidence.toFixed(2))}`,
|
|
220
|
+
`fts=${chalk.dim(bd.ftsBoost.toFixed(2))}`
|
|
221
|
+
];
|
|
222
|
+
console.log(` ${chalk.dim(parts.join(' '))}`);
|
|
223
|
+
}
|
|
224
|
+
console.log('');
|
|
225
|
+
});
|
|
164
226
|
|
|
165
227
|
db.close();
|
|
166
228
|
} catch (error) {
|
|
167
|
-
|
|
229
|
+
f.error(error.message);
|
|
168
230
|
process.exit(1);
|
|
169
231
|
}
|
|
170
232
|
});
|
|
@@ -174,7 +236,9 @@ program
|
|
|
174
236
|
.command('forget <id>')
|
|
175
237
|
.description('Delete a memory by ID')
|
|
176
238
|
.option('--config <path>', 'Path to config file')
|
|
177
|
-
.action((id, options) => {
|
|
239
|
+
.action(async (id, options) => {
|
|
240
|
+
const f = await loadFormat();
|
|
241
|
+
const chalk = (await import('chalk')).default;
|
|
178
242
|
try {
|
|
179
243
|
const config = loadConfig(options.config);
|
|
180
244
|
const db = initDatabase(getDatabasePath(config));
|
|
@@ -182,23 +246,32 @@ program
|
|
|
182
246
|
const memory = getMemory(db, id);
|
|
183
247
|
|
|
184
248
|
if (!memory) {
|
|
185
|
-
|
|
249
|
+
f.error(`Memory not found: ${id}`);
|
|
186
250
|
db.close();
|
|
187
251
|
process.exit(1);
|
|
188
252
|
}
|
|
189
253
|
|
|
254
|
+
// Show preview before deletion
|
|
255
|
+
console.log('');
|
|
256
|
+
console.log(f.box(
|
|
257
|
+
`${chalk.bold('Deleting memory')} ${f.shortId(memory.id)}\n` +
|
|
258
|
+
`${f.categoryBadge(memory.category)} conf ${f.confidenceColor(memory.confidence)}\n` +
|
|
259
|
+
`${f.truncate(memory.content, 60)}`
|
|
260
|
+
));
|
|
261
|
+
|
|
190
262
|
const deleted = deleteMemory(db, id);
|
|
191
263
|
|
|
264
|
+
console.log('');
|
|
192
265
|
if (deleted) {
|
|
193
|
-
|
|
194
|
-
console.log(`Content: ${memory.content}`);
|
|
266
|
+
f.success(`Memory deleted: ${id}`);
|
|
195
267
|
} else {
|
|
196
|
-
|
|
268
|
+
f.error(`Failed to delete memory: ${id}`);
|
|
197
269
|
}
|
|
270
|
+
console.log('');
|
|
198
271
|
|
|
199
272
|
db.close();
|
|
200
273
|
} catch (error) {
|
|
201
|
-
|
|
274
|
+
f.error(error.message);
|
|
202
275
|
process.exit(1);
|
|
203
276
|
}
|
|
204
277
|
});
|
|
@@ -207,12 +280,13 @@ program
|
|
|
207
280
|
program
|
|
208
281
|
.command('list')
|
|
209
282
|
.description('List all memories (paginated)')
|
|
210
|
-
.option('-l, --limit <n>', 'Max results', parseInt, 50)
|
|
211
|
-
.option('--offset <n>', 'Offset for pagination', parseInt, 0)
|
|
283
|
+
.option('-l, --limit <n>', 'Max results', v => parseInt(v, 10), 50)
|
|
284
|
+
.option('--offset <n>', 'Offset for pagination', v => parseInt(v, 10), 0)
|
|
212
285
|
.option('-c, --category <type>', 'Filter by category')
|
|
213
286
|
.option('-n, --namespace <name>', 'Filter by namespace')
|
|
214
287
|
.option('--config <path>', 'Path to config file')
|
|
215
|
-
.action((options) => {
|
|
288
|
+
.action(async (options) => {
|
|
289
|
+
const f = await loadFormat();
|
|
216
290
|
try {
|
|
217
291
|
const config = loadConfig(options.config);
|
|
218
292
|
const db = initDatabase(getDatabasePath(config));
|
|
@@ -224,19 +298,37 @@ program
|
|
|
224
298
|
namespace: options.namespace
|
|
225
299
|
});
|
|
226
300
|
|
|
227
|
-
|
|
301
|
+
f.printSection(`${memories.length} memories`);
|
|
228
302
|
|
|
229
|
-
memories.
|
|
230
|
-
console.log(`[${index + 1}] ${memory.id.substring(0, 8)}`);
|
|
231
|
-
console.log(` ${memory.content}`);
|
|
232
|
-
console.log(` Category: ${memory.category} | Entity: ${memory.entity || 'none'} | Confidence: ${memory.confidence}`);
|
|
233
|
-
console.log(` Namespace: ${memory.namespace} | Accessed: ${memory.access_count} times`);
|
|
303
|
+
if (memories.length === 0) {
|
|
234
304
|
console.log('');
|
|
305
|
+
f.info('No memories found.');
|
|
306
|
+
console.log('');
|
|
307
|
+
db.close();
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const table = f.createTable({
|
|
312
|
+
head: ['ID', 'Content', 'Category', 'Conf', 'Access', 'Namespace']
|
|
235
313
|
});
|
|
236
314
|
|
|
315
|
+
for (const memory of memories) {
|
|
316
|
+
table.push([
|
|
317
|
+
f.shortId(memory.id),
|
|
318
|
+
f.truncate(memory.content, 50),
|
|
319
|
+
f.categoryBadge(memory.category),
|
|
320
|
+
f.confidenceColor(memory.confidence),
|
|
321
|
+
String(memory.access_count),
|
|
322
|
+
memory.namespace
|
|
323
|
+
]);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
console.log(table.toString());
|
|
327
|
+
console.log('');
|
|
328
|
+
|
|
237
329
|
db.close();
|
|
238
330
|
} catch (error) {
|
|
239
|
-
|
|
331
|
+
f.error(error.message);
|
|
240
332
|
process.exit(1);
|
|
241
333
|
}
|
|
242
334
|
});
|
|
@@ -247,50 +339,86 @@ program
|
|
|
247
339
|
.description('Show Engram status and statistics')
|
|
248
340
|
.option('--config <path>', 'Path to config file')
|
|
249
341
|
.action(async (options) => {
|
|
342
|
+
const f = await loadFormat();
|
|
343
|
+
const chalk = (await import('chalk')).default;
|
|
250
344
|
try {
|
|
251
345
|
const config = loadConfig(options.config);
|
|
252
346
|
const db = initDatabase(getDatabasePath(config));
|
|
253
347
|
const stats = getStats(db);
|
|
254
348
|
|
|
255
|
-
|
|
349
|
+
f.printHeader(version);
|
|
256
350
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
console.log(
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
351
|
+
// Memory statistics
|
|
352
|
+
f.printSection('Memory Statistics');
|
|
353
|
+
console.log('');
|
|
354
|
+
f.printKeyValue([
|
|
355
|
+
['Total memories', chalk.bold(String(stats.total))],
|
|
356
|
+
['With embeddings', String(stats.withEmbeddings)]
|
|
357
|
+
]);
|
|
358
|
+
|
|
359
|
+
// Categories table
|
|
360
|
+
if (Object.keys(stats.byCategory).length > 0) {
|
|
361
|
+
console.log('');
|
|
362
|
+
const catTable = f.createTable({ head: ['Category', 'Count'] });
|
|
363
|
+
for (const [category, count] of Object.entries(stats.byCategory)) {
|
|
364
|
+
catTable.push([f.categoryBadge(category), String(count)]);
|
|
365
|
+
}
|
|
366
|
+
console.log(catTable.toString());
|
|
263
367
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
368
|
+
|
|
369
|
+
// Namespaces table
|
|
370
|
+
if (Object.keys(stats.byNamespace).length > 0) {
|
|
371
|
+
console.log('');
|
|
372
|
+
const nsTable = f.createTable({ head: ['Namespace', 'Count'] });
|
|
373
|
+
for (const [namespace, count] of Object.entries(stats.byNamespace)) {
|
|
374
|
+
nsTable.push([namespace, String(count)]);
|
|
375
|
+
}
|
|
376
|
+
console.log(nsTable.toString());
|
|
267
377
|
}
|
|
268
378
|
|
|
269
379
|
// Model info
|
|
380
|
+
f.printSection('Embedding Model');
|
|
381
|
+
console.log('');
|
|
270
382
|
try {
|
|
271
383
|
const { getModelInfo } = await import('../src/embed/index.js');
|
|
272
384
|
const modelInfo = getModelInfo(getModelsPath(config));
|
|
273
385
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
386
|
+
let statusText;
|
|
387
|
+
if (modelInfo.cached) {
|
|
388
|
+
statusText = chalk.green('Ready');
|
|
389
|
+
} else if (modelInfo.loading) {
|
|
390
|
+
statusText = chalk.yellow('Loading...');
|
|
391
|
+
} else if (modelInfo.available) {
|
|
392
|
+
statusText = chalk.green('Available');
|
|
393
|
+
} else {
|
|
394
|
+
statusText = chalk.red('Not available');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
f.printKeyValue([
|
|
398
|
+
['Name', modelInfo.name],
|
|
399
|
+
['Status', statusText],
|
|
400
|
+
['Size', `${modelInfo.sizeMB} MB`],
|
|
401
|
+
['Path', chalk.dim(modelInfo.path)]
|
|
402
|
+
]);
|
|
403
|
+
} catch {
|
|
404
|
+
f.printKeyValue([['Status', chalk.red('Not available')]]);
|
|
282
405
|
}
|
|
283
406
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
console.log(
|
|
287
|
-
|
|
288
|
-
|
|
407
|
+
// Configuration
|
|
408
|
+
f.printSection('Configuration');
|
|
409
|
+
console.log('');
|
|
410
|
+
f.printKeyValue([
|
|
411
|
+
['Data directory', chalk.dim(config.dataDir)],
|
|
412
|
+
['Namespace', config.defaults.namespace],
|
|
413
|
+
['Recall limit', String(config.defaults.recallLimit)],
|
|
414
|
+
['Secret detection', config.security.secretDetection ? chalk.green('on') : chalk.red('off')]
|
|
415
|
+
]);
|
|
289
416
|
console.log('');
|
|
290
417
|
|
|
291
418
|
db.close();
|
|
292
419
|
} catch (error) {
|
|
293
|
-
|
|
420
|
+
const f2 = await loadFormat();
|
|
421
|
+
f2.error(error.message);
|
|
294
422
|
process.exit(1);
|
|
295
423
|
}
|
|
296
424
|
});
|
|
@@ -305,11 +433,13 @@ program
|
|
|
305
433
|
.option('--cleanup-stale', 'Enable stale memory cleanup')
|
|
306
434
|
.option('--config <path>', 'Path to config file')
|
|
307
435
|
.action(async (options) => {
|
|
436
|
+
const f = await loadFormat();
|
|
308
437
|
try {
|
|
309
438
|
const config = loadConfig(options.config);
|
|
310
439
|
const db = initDatabase(getDatabasePath(config));
|
|
311
440
|
|
|
312
|
-
|
|
441
|
+
const spin = f.spinner('Running consolidation...');
|
|
442
|
+
spin.start();
|
|
313
443
|
|
|
314
444
|
const results = await consolidate(db, {
|
|
315
445
|
detectDuplicates: options.duplicates !== false,
|
|
@@ -318,17 +448,23 @@ program
|
|
|
318
448
|
cleanupStale: options.cleanupStale === true
|
|
319
449
|
});
|
|
320
450
|
|
|
321
|
-
|
|
322
|
-
console.log(
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
451
|
+
spin.succeed('Consolidation complete');
|
|
452
|
+
console.log('');
|
|
453
|
+
|
|
454
|
+
const table = f.createTable({ head: ['Metric', 'Count'] });
|
|
455
|
+
table.push(
|
|
456
|
+
['Duplicates removed', String(results.duplicatesRemoved)],
|
|
457
|
+
['Contradictions detected', String(results.contradictionsDetected)],
|
|
458
|
+
['Memories decayed', String(results.memoriesDecayed)],
|
|
459
|
+
['Stale cleaned', String(results.staleMemoriesCleaned)],
|
|
460
|
+
['Duration', `${results.duration}ms`]
|
|
461
|
+
);
|
|
462
|
+
console.log(table.toString());
|
|
327
463
|
console.log('');
|
|
328
464
|
|
|
329
465
|
db.close();
|
|
330
466
|
} catch (error) {
|
|
331
|
-
|
|
467
|
+
f.error(error.message);
|
|
332
468
|
process.exit(1);
|
|
333
469
|
}
|
|
334
470
|
});
|
|
@@ -338,7 +474,9 @@ program
|
|
|
338
474
|
.command('conflicts')
|
|
339
475
|
.description('Show detected contradictions')
|
|
340
476
|
.option('--config <path>', 'Path to config file')
|
|
341
|
-
.action((options) => {
|
|
477
|
+
.action(async (options) => {
|
|
478
|
+
const f = await loadFormat();
|
|
479
|
+
const chalk = (await import('chalk')).default;
|
|
342
480
|
try {
|
|
343
481
|
const config = loadConfig(options.config);
|
|
344
482
|
const db = initDatabase(getDatabasePath(config));
|
|
@@ -346,22 +484,28 @@ program
|
|
|
346
484
|
const conflicts = getConflicts(db);
|
|
347
485
|
|
|
348
486
|
if (conflicts.length === 0) {
|
|
349
|
-
console.log('
|
|
487
|
+
console.log('');
|
|
488
|
+
f.success('No conflicts detected');
|
|
489
|
+
console.log('');
|
|
350
490
|
} else {
|
|
351
|
-
|
|
491
|
+
f.printSection(`${conflicts.length} conflict${conflicts.length === 1 ? '' : 's'}`);
|
|
492
|
+
console.log('');
|
|
352
493
|
|
|
353
494
|
conflicts.forEach((conflict, index) => {
|
|
354
|
-
|
|
355
|
-
conflict.memories.
|
|
356
|
-
|
|
357
|
-
|
|
495
|
+
const header = `${chalk.bold(`Conflict ${index + 1}`)} ${chalk.dim(conflict.conflictId)}`;
|
|
496
|
+
const memLines = conflict.memories.map((memory, mIndex) => {
|
|
497
|
+
const label = chalk.bold(String.fromCharCode(65 + mIndex));
|
|
498
|
+
return `${label} ${f.shortId(memory.id)} ${f.categoryBadge(memory.category || 'fact')}\n ${f.truncate(memory.content, 56)}`;
|
|
499
|
+
}).join('\n');
|
|
500
|
+
|
|
501
|
+
console.log(f.box(`${header}\n${memLines}`));
|
|
358
502
|
console.log('');
|
|
359
503
|
});
|
|
360
504
|
}
|
|
361
505
|
|
|
362
506
|
db.close();
|
|
363
507
|
} catch (error) {
|
|
364
|
-
|
|
508
|
+
f.error(error.message);
|
|
365
509
|
process.exit(1);
|
|
366
510
|
}
|
|
367
511
|
});
|
|
@@ -375,13 +519,15 @@ program
|
|
|
375
519
|
.option('-f, --format <type>', 'Format: markdown, claude, txt, json', 'markdown')
|
|
376
520
|
.option('-c, --categories <list>', 'Comma-separated list of categories')
|
|
377
521
|
.option('--min-confidence <score>', 'Minimum confidence (0-1)', parseFloat, 0.5)
|
|
378
|
-
.option('--min-access <count>', 'Minimum access count', parseInt, 0)
|
|
522
|
+
.option('--min-access <count>', 'Minimum access count', v => parseInt(v, 10), 0)
|
|
379
523
|
.option('--include-low-feedback', 'Include memories with negative feedback', false)
|
|
380
524
|
.option('--group-by <field>', 'Group by: category, entity, none', 'category')
|
|
381
525
|
.option('--header <text>', 'Custom header text')
|
|
382
526
|
.option('--footer <text>', 'Custom footer text')
|
|
383
527
|
.option('--config <path>', 'Path to config file')
|
|
384
|
-
.action((options) => {
|
|
528
|
+
.action(async (options) => {
|
|
529
|
+
const f = await loadFormat();
|
|
530
|
+
const chalk = (await import('chalk')).default;
|
|
385
531
|
try {
|
|
386
532
|
const config = loadConfig(options.config);
|
|
387
533
|
const db = initDatabase(getDatabasePath(config));
|
|
@@ -403,17 +549,56 @@ program
|
|
|
403
549
|
if (options.output) {
|
|
404
550
|
// Write to file
|
|
405
551
|
fs.writeFileSync(options.output, result.content, 'utf8');
|
|
406
|
-
console.log(
|
|
407
|
-
|
|
408
|
-
console.log(
|
|
552
|
+
console.log('');
|
|
553
|
+
f.success(`Exported to ${chalk.bold(options.output)}`);
|
|
554
|
+
console.log('');
|
|
555
|
+
f.printKeyValue([
|
|
556
|
+
['Memories', String(result.stats.totalExported)],
|
|
557
|
+
['Size', `${result.stats.sizeKB} KB`],
|
|
558
|
+
['Format', options.format]
|
|
559
|
+
]);
|
|
560
|
+
|
|
561
|
+
if (result.stats.byCategory && Object.keys(result.stats.byCategory).length > 0) {
|
|
562
|
+
console.log('');
|
|
563
|
+
const catTable = f.createTable({ head: ['Category', 'Count'] });
|
|
564
|
+
for (const [cat, count] of Object.entries(result.stats.byCategory)) {
|
|
565
|
+
catTable.push([f.categoryBadge(cat), String(count)]);
|
|
566
|
+
}
|
|
567
|
+
console.log(catTable.toString());
|
|
568
|
+
}
|
|
569
|
+
console.log('');
|
|
409
570
|
} else {
|
|
410
|
-
// Output to stdout
|
|
571
|
+
// Output to stdout (raw, no formatting)
|
|
411
572
|
console.log(result.content);
|
|
412
573
|
}
|
|
413
574
|
|
|
414
575
|
db.close();
|
|
415
576
|
} catch (error) {
|
|
416
|
-
|
|
577
|
+
f.error(error.message);
|
|
578
|
+
process.exit(1);
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
// Import wizard command
|
|
583
|
+
program
|
|
584
|
+
.command('import')
|
|
585
|
+
.description('Import memories from developer artifacts (smart import wizard)')
|
|
586
|
+
.option('-s, --source <type>', 'Single source: cursorrules, claude, package, git, ssh, shell, obsidian, env')
|
|
587
|
+
.option('--dry-run', 'Preview without committing')
|
|
588
|
+
.option('-n, --namespace <name>', 'Override namespace for imported memories')
|
|
589
|
+
.option('--config <path>', 'Path to config file')
|
|
590
|
+
.action(async (options) => {
|
|
591
|
+
try {
|
|
592
|
+
const { runWizard } = await import('../src/import/wizard.js');
|
|
593
|
+
await runWizard({
|
|
594
|
+
source: options.source,
|
|
595
|
+
dryRun: options.dryRun,
|
|
596
|
+
namespace: options.namespace,
|
|
597
|
+
config: options.config
|
|
598
|
+
});
|
|
599
|
+
} catch (error) {
|
|
600
|
+
const f = await loadFormat();
|
|
601
|
+
f.error(`Import error: ${error.message}`);
|
|
417
602
|
process.exit(1);
|
|
418
603
|
}
|
|
419
604
|
});
|