@akashabot/openclaw-mem 0.2.0 → 0.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/dist/cli.js +202 -1
- package/package.json +2 -2
- package/src/cli.ts +230 -0
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,11 @@ import { Command } from 'commander';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import { v4 as uuidv4 } from 'uuid';
|
|
5
5
|
import * as core from '@akashabot/openclaw-memory-offline-core';
|
|
6
|
-
const { addItem, hybridSearch, hybridSearchFiltered, initSchema, openDb, runMigrations, searchItems, getMemoriesByEntity, getMemoriesBySession, getMemoriesByProcess, listEntities, listSessions,
|
|
6
|
+
const { addItem, hybridSearch, hybridSearchFiltered, initSchema, openDb, runMigrations, searchItems, getMemoriesByEntity, getMemoriesBySession, getMemoriesByProcess, listEntities, listSessions,
|
|
7
|
+
// Phase 2: Facts
|
|
8
|
+
insertFact, getFactsBySubject, getFactsByPredicate, searchFacts, getAllFacts, listSubjects, listPredicates, deleteFact, extractFactsSimple,
|
|
9
|
+
// Phase 3: Knowledge Graph
|
|
10
|
+
getEntityGraph, getRelatedEntities, findPaths, getGraphStats, exportGraphJson, searchEntities, } = core;
|
|
7
11
|
const program = new Command();
|
|
8
12
|
program
|
|
9
13
|
.name('openclaw-mem')
|
|
@@ -204,4 +208,201 @@ program
|
|
|
204
208
|
console.log(JSON.stringify({ ok: true, sessionId, count: items.length, items }));
|
|
205
209
|
});
|
|
206
210
|
});
|
|
211
|
+
// ============================================================================
|
|
212
|
+
// Phase 2: Fact commands
|
|
213
|
+
// ============================================================================
|
|
214
|
+
program
|
|
215
|
+
.command('add-fact <subject> <predicate> <object>')
|
|
216
|
+
.description('Add a structured fact (subject, predicate, object)')
|
|
217
|
+
.option('--confidence <n>', 'Confidence level 0-1 (default 0.7)', '0.7')
|
|
218
|
+
.option('--entity-id <entityId>', 'Who said/wrote this fact')
|
|
219
|
+
.option('--source-item-id <itemId>', 'Source memory item ID')
|
|
220
|
+
.action((subject, predicate, object, cmdOpts) => {
|
|
221
|
+
withDb((dbPath) => {
|
|
222
|
+
const db = openDb(dbPath);
|
|
223
|
+
initSchema(db);
|
|
224
|
+
const id = uuidv4();
|
|
225
|
+
const confidence = Math.max(0, Math.min(1, Number(cmdOpts.confidence ?? 0.7)));
|
|
226
|
+
const fact = insertFact(db, {
|
|
227
|
+
id,
|
|
228
|
+
subject,
|
|
229
|
+
predicate,
|
|
230
|
+
object,
|
|
231
|
+
confidence,
|
|
232
|
+
source_item_id: cmdOpts.sourceItemId ?? null,
|
|
233
|
+
entity_id: cmdOpts.entityId ?? null,
|
|
234
|
+
});
|
|
235
|
+
console.log(JSON.stringify({ ok: true, fact }));
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
program
|
|
239
|
+
.command('list-facts')
|
|
240
|
+
.description('List all facts (optionally filtered by entity)')
|
|
241
|
+
.option('--entity-id <entityId>', 'Filter by entity')
|
|
242
|
+
.option('--limit <n>', 'Max results (default 50)', '50')
|
|
243
|
+
.action((cmdOpts) => {
|
|
244
|
+
withDb((dbPath) => {
|
|
245
|
+
const db = openDb(dbPath);
|
|
246
|
+
initSchema(db);
|
|
247
|
+
const limit = Math.max(1, Math.min(500, Number(cmdOpts.limit ?? 50)));
|
|
248
|
+
const facts = getAllFacts(db, cmdOpts.entityId, limit);
|
|
249
|
+
console.log(JSON.stringify({ ok: true, count: facts.length, facts }));
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
program
|
|
253
|
+
.command('get-facts-by-subject <subject>')
|
|
254
|
+
.description('Get all facts about a specific subject')
|
|
255
|
+
.option('--limit <n>', 'Max results (default 50)', '50')
|
|
256
|
+
.action((subject, cmdOpts) => {
|
|
257
|
+
withDb((dbPath) => {
|
|
258
|
+
const db = openDb(dbPath);
|
|
259
|
+
initSchema(db);
|
|
260
|
+
const limit = Math.max(1, Math.min(500, Number(cmdOpts.limit ?? 50)));
|
|
261
|
+
const facts = getFactsBySubject(db, subject, limit);
|
|
262
|
+
console.log(JSON.stringify({ ok: true, subject, count: facts.length, facts }));
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
program
|
|
266
|
+
.command('search-facts <query>')
|
|
267
|
+
.description('Search facts by subject, predicate, or object')
|
|
268
|
+
.option('--limit <n>', 'Max results (default 50)', '50')
|
|
269
|
+
.action((query, cmdOpts) => {
|
|
270
|
+
withDb((dbPath) => {
|
|
271
|
+
const db = openDb(dbPath);
|
|
272
|
+
initSchema(db);
|
|
273
|
+
const limit = Math.max(1, Math.min(500, Number(cmdOpts.limit ?? 50)));
|
|
274
|
+
const facts = searchFacts(db, query, limit);
|
|
275
|
+
console.log(JSON.stringify({ ok: true, query, count: facts.length, facts }));
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
program
|
|
279
|
+
.command('list-subjects')
|
|
280
|
+
.description('List all distinct subjects in the facts table')
|
|
281
|
+
.action(() => {
|
|
282
|
+
withDb((dbPath) => {
|
|
283
|
+
const db = openDb(dbPath);
|
|
284
|
+
initSchema(db);
|
|
285
|
+
const subjects = listSubjects(db);
|
|
286
|
+
console.log(JSON.stringify({ ok: true, subjects, count: subjects.length }));
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
program
|
|
290
|
+
.command('list-predicates')
|
|
291
|
+
.description('List all distinct predicates in the facts table')
|
|
292
|
+
.action(() => {
|
|
293
|
+
withDb((dbPath) => {
|
|
294
|
+
const db = openDb(dbPath);
|
|
295
|
+
initSchema(db);
|
|
296
|
+
const predicates = listPredicates(db);
|
|
297
|
+
console.log(JSON.stringify({ ok: true, predicates, count: predicates.length }));
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
program
|
|
301
|
+
.command('delete-fact <factId>')
|
|
302
|
+
.description('Delete a fact by ID')
|
|
303
|
+
.action((factId) => {
|
|
304
|
+
withDb((dbPath) => {
|
|
305
|
+
const db = openDb(dbPath);
|
|
306
|
+
initSchema(db);
|
|
307
|
+
const deleted = deleteFact(db, factId);
|
|
308
|
+
console.log(JSON.stringify({ ok: true, deleted, id: factId }));
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
program
|
|
312
|
+
.command('extract-facts [text]')
|
|
313
|
+
.description('Extract potential facts from text (pattern-based, does not store)')
|
|
314
|
+
.action((text) => {
|
|
315
|
+
const input = text ?? fs.readFileSync(0, 'utf-8');
|
|
316
|
+
const facts = extractFactsSimple(input);
|
|
317
|
+
console.log(JSON.stringify({ ok: true, count: facts.length, facts }));
|
|
318
|
+
});
|
|
319
|
+
// ============================================================================
|
|
320
|
+
// Phase 3: Knowledge Graph commands
|
|
321
|
+
// ============================================================================
|
|
322
|
+
program
|
|
323
|
+
.command('graph-stats')
|
|
324
|
+
.description('Get statistics about the knowledge graph')
|
|
325
|
+
.action(() => {
|
|
326
|
+
withDb((dbPath) => {
|
|
327
|
+
const db = openDb(dbPath);
|
|
328
|
+
initSchema(db);
|
|
329
|
+
const stats = getGraphStats(db);
|
|
330
|
+
console.log(JSON.stringify({ ok: true, stats }, null, 2));
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
program
|
|
334
|
+
.command('graph-entity <entity>')
|
|
335
|
+
.description('Get all facts connected to an entity (as subject or object)')
|
|
336
|
+
.action((entity) => {
|
|
337
|
+
withDb((dbPath) => {
|
|
338
|
+
const db = openDb(dbPath);
|
|
339
|
+
initSchema(db);
|
|
340
|
+
const edges = getEntityGraph(db, entity);
|
|
341
|
+
console.log(JSON.stringify({ ok: true, entity, count: edges.length, edges }));
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
program
|
|
345
|
+
.command('graph-related <entity>')
|
|
346
|
+
.description('Get all entities directly connected to an entity')
|
|
347
|
+
.action((entity) => {
|
|
348
|
+
withDb((dbPath) => {
|
|
349
|
+
const db = openDb(dbPath);
|
|
350
|
+
initSchema(db);
|
|
351
|
+
const related = getRelatedEntities(db, entity);
|
|
352
|
+
console.log(JSON.stringify({ ok: true, entity, count: related.length, related }));
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
program
|
|
356
|
+
.command('graph-path <fromEntity> <toEntity>')
|
|
357
|
+
.description('Find paths between two entities in the knowledge graph')
|
|
358
|
+
.option('--max-depth <n>', 'Maximum path depth (default 4)', '4')
|
|
359
|
+
.option('--max-paths <n>', 'Maximum number of paths to return (default 5)', '5')
|
|
360
|
+
.action((fromEntity, toEntity, cmdOpts) => {
|
|
361
|
+
withDb((dbPath) => {
|
|
362
|
+
const db = openDb(dbPath);
|
|
363
|
+
initSchema(db);
|
|
364
|
+
const maxDepth = Math.max(1, Math.min(10, Number(cmdOpts.maxDepth ?? 4)));
|
|
365
|
+
const maxPaths = Math.max(1, Math.min(20, Number(cmdOpts.maxPaths ?? 5)));
|
|
366
|
+
const paths = findPaths(db, fromEntity, toEntity, maxDepth, maxPaths);
|
|
367
|
+
console.log(JSON.stringify({ ok: true, from: fromEntity, to: toEntity, count: paths.length, paths }));
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
program
|
|
371
|
+
.command('graph-export [outputFile]')
|
|
372
|
+
.description('Export the knowledge graph as JSON (for visualization)')
|
|
373
|
+
.option('--limit <n>', 'Max edges to export (default 1000)', '1000')
|
|
374
|
+
.option('--min-confidence <n>', 'Minimum confidence threshold (default 0)', '0')
|
|
375
|
+
.option('--entity <entity>', 'Export only subgraph around this entity')
|
|
376
|
+
.action((outputFile, cmdOpts) => {
|
|
377
|
+
withDb((dbPath) => {
|
|
378
|
+
const db = openDb(dbPath);
|
|
379
|
+
initSchema(db);
|
|
380
|
+
const graph = exportGraphJson(db, {
|
|
381
|
+
limit: Number(cmdOpts.limit ?? 1000),
|
|
382
|
+
minConfidence: Number(cmdOpts.minConfidence ?? 0),
|
|
383
|
+
entity: cmdOpts.entity,
|
|
384
|
+
});
|
|
385
|
+
const output = JSON.stringify({ ok: true, graph }, null, 2);
|
|
386
|
+
if (outputFile) {
|
|
387
|
+
fs.writeFileSync(outputFile, output);
|
|
388
|
+
console.log(JSON.stringify({ ok: true, file: outputFile, nodes: graph.nodes.length, edges: graph.edges.length }));
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
console.log(output);
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
program
|
|
396
|
+
.command('search-entities <pattern>')
|
|
397
|
+
.description('Search for entities matching a pattern')
|
|
398
|
+
.option('--limit <n>', 'Max results (default 50)', '50')
|
|
399
|
+
.action((pattern, cmdOpts) => {
|
|
400
|
+
withDb((dbPath) => {
|
|
401
|
+
const db = openDb(dbPath);
|
|
402
|
+
initSchema(db);
|
|
403
|
+
const limit = Math.max(1, Math.min(500, Number(cmdOpts.limit ?? 50)));
|
|
404
|
+
const entities = searchEntities(db, pattern, limit);
|
|
405
|
+
console.log(JSON.stringify({ ok: true, pattern, count: entities.length, entities }));
|
|
406
|
+
});
|
|
407
|
+
});
|
|
207
408
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akashabot/openclaw-mem",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI for OpenClaw Offline Memory (SQLite FTS + optional embeddings)",
|
|
6
6
|
"bin": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"commander": "^12.1.0",
|
|
19
19
|
"uuid": "^10.0.0",
|
|
20
|
-
"@akashabot/openclaw-memory-offline-core": "^0.
|
|
20
|
+
"@akashabot/openclaw-memory-offline-core": "^0.4.0"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@types/uuid": "^10.0.0"
|
package/src/cli.ts
CHANGED
|
@@ -17,6 +17,23 @@ const {
|
|
|
17
17
|
getMemoriesByProcess,
|
|
18
18
|
listEntities,
|
|
19
19
|
listSessions,
|
|
20
|
+
// Phase 2: Facts
|
|
21
|
+
insertFact,
|
|
22
|
+
getFactsBySubject,
|
|
23
|
+
getFactsByPredicate,
|
|
24
|
+
searchFacts,
|
|
25
|
+
getAllFacts,
|
|
26
|
+
listSubjects,
|
|
27
|
+
listPredicates,
|
|
28
|
+
deleteFact,
|
|
29
|
+
extractFactsSimple,
|
|
30
|
+
// Phase 3: Knowledge Graph
|
|
31
|
+
getEntityGraph,
|
|
32
|
+
getRelatedEntities,
|
|
33
|
+
findPaths,
|
|
34
|
+
getGraphStats,
|
|
35
|
+
exportGraphJson,
|
|
36
|
+
searchEntities,
|
|
20
37
|
} = core;
|
|
21
38
|
|
|
22
39
|
const program = new Command();
|
|
@@ -254,4 +271,217 @@ program
|
|
|
254
271
|
});
|
|
255
272
|
});
|
|
256
273
|
|
|
274
|
+
// ============================================================================
|
|
275
|
+
// Phase 2: Fact commands
|
|
276
|
+
// ============================================================================
|
|
277
|
+
|
|
278
|
+
program
|
|
279
|
+
.command('add-fact <subject> <predicate> <object>')
|
|
280
|
+
.description('Add a structured fact (subject, predicate, object)')
|
|
281
|
+
.option('--confidence <n>', 'Confidence level 0-1 (default 0.7)', '0.7')
|
|
282
|
+
.option('--entity-id <entityId>', 'Who said/wrote this fact')
|
|
283
|
+
.option('--source-item-id <itemId>', 'Source memory item ID')
|
|
284
|
+
.action((subject: string, predicate: string, object: string, cmdOpts) => {
|
|
285
|
+
withDb((dbPath) => {
|
|
286
|
+
const db = openDb(dbPath);
|
|
287
|
+
initSchema(db);
|
|
288
|
+
const id = uuidv4();
|
|
289
|
+
const confidence = Math.max(0, Math.min(1, Number(cmdOpts.confidence ?? 0.7)));
|
|
290
|
+
const fact = insertFact(db, {
|
|
291
|
+
id,
|
|
292
|
+
subject,
|
|
293
|
+
predicate,
|
|
294
|
+
object,
|
|
295
|
+
confidence,
|
|
296
|
+
source_item_id: cmdOpts.sourceItemId ?? null,
|
|
297
|
+
entity_id: cmdOpts.entityId ?? null,
|
|
298
|
+
});
|
|
299
|
+
console.log(JSON.stringify({ ok: true, fact }));
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
program
|
|
304
|
+
.command('list-facts')
|
|
305
|
+
.description('List all facts (optionally filtered by entity)')
|
|
306
|
+
.option('--entity-id <entityId>', 'Filter by entity')
|
|
307
|
+
.option('--limit <n>', 'Max results (default 50)', '50')
|
|
308
|
+
.action((cmdOpts) => {
|
|
309
|
+
withDb((dbPath) => {
|
|
310
|
+
const db = openDb(dbPath);
|
|
311
|
+
initSchema(db);
|
|
312
|
+
const limit = Math.max(1, Math.min(500, Number(cmdOpts.limit ?? 50)));
|
|
313
|
+
const facts = getAllFacts(db, cmdOpts.entityId, limit);
|
|
314
|
+
console.log(JSON.stringify({ ok: true, count: facts.length, facts }));
|
|
315
|
+
});
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
program
|
|
319
|
+
.command('get-facts-by-subject <subject>')
|
|
320
|
+
.description('Get all facts about a specific subject')
|
|
321
|
+
.option('--limit <n>', 'Max results (default 50)', '50')
|
|
322
|
+
.action((subject: string, cmdOpts) => {
|
|
323
|
+
withDb((dbPath) => {
|
|
324
|
+
const db = openDb(dbPath);
|
|
325
|
+
initSchema(db);
|
|
326
|
+
const limit = Math.max(1, Math.min(500, Number(cmdOpts.limit ?? 50)));
|
|
327
|
+
const facts = getFactsBySubject(db, subject, limit);
|
|
328
|
+
console.log(JSON.stringify({ ok: true, subject, count: facts.length, facts }));
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
program
|
|
333
|
+
.command('search-facts <query>')
|
|
334
|
+
.description('Search facts by subject, predicate, or object')
|
|
335
|
+
.option('--limit <n>', 'Max results (default 50)', '50')
|
|
336
|
+
.action((query: string, cmdOpts) => {
|
|
337
|
+
withDb((dbPath) => {
|
|
338
|
+
const db = openDb(dbPath);
|
|
339
|
+
initSchema(db);
|
|
340
|
+
const limit = Math.max(1, Math.min(500, Number(cmdOpts.limit ?? 50)));
|
|
341
|
+
const facts = searchFacts(db, query, limit);
|
|
342
|
+
console.log(JSON.stringify({ ok: true, query, count: facts.length, facts }));
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
program
|
|
347
|
+
.command('list-subjects')
|
|
348
|
+
.description('List all distinct subjects in the facts table')
|
|
349
|
+
.action(() => {
|
|
350
|
+
withDb((dbPath) => {
|
|
351
|
+
const db = openDb(dbPath);
|
|
352
|
+
initSchema(db);
|
|
353
|
+
const subjects = listSubjects(db);
|
|
354
|
+
console.log(JSON.stringify({ ok: true, subjects, count: subjects.length }));
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
program
|
|
359
|
+
.command('list-predicates')
|
|
360
|
+
.description('List all distinct predicates in the facts table')
|
|
361
|
+
.action(() => {
|
|
362
|
+
withDb((dbPath) => {
|
|
363
|
+
const db = openDb(dbPath);
|
|
364
|
+
initSchema(db);
|
|
365
|
+
const predicates = listPredicates(db);
|
|
366
|
+
console.log(JSON.stringify({ ok: true, predicates, count: predicates.length }));
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
program
|
|
371
|
+
.command('delete-fact <factId>')
|
|
372
|
+
.description('Delete a fact by ID')
|
|
373
|
+
.action((factId: string) => {
|
|
374
|
+
withDb((dbPath) => {
|
|
375
|
+
const db = openDb(dbPath);
|
|
376
|
+
initSchema(db);
|
|
377
|
+
const deleted = deleteFact(db, factId);
|
|
378
|
+
console.log(JSON.stringify({ ok: true, deleted, id: factId }));
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
program
|
|
383
|
+
.command('extract-facts [text]')
|
|
384
|
+
.description('Extract potential facts from text (pattern-based, does not store)')
|
|
385
|
+
.action((text: string | undefined) => {
|
|
386
|
+
const input = text ?? fs.readFileSync(0, 'utf-8');
|
|
387
|
+
const facts = extractFactsSimple(input);
|
|
388
|
+
console.log(JSON.stringify({ ok: true, count: facts.length, facts }));
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
// ============================================================================
|
|
392
|
+
// Phase 3: Knowledge Graph commands
|
|
393
|
+
// ============================================================================
|
|
394
|
+
|
|
395
|
+
program
|
|
396
|
+
.command('graph-stats')
|
|
397
|
+
.description('Get statistics about the knowledge graph')
|
|
398
|
+
.action(() => {
|
|
399
|
+
withDb((dbPath) => {
|
|
400
|
+
const db = openDb(dbPath);
|
|
401
|
+
initSchema(db);
|
|
402
|
+
const stats = getGraphStats(db);
|
|
403
|
+
console.log(JSON.stringify({ ok: true, stats }, null, 2));
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
program
|
|
408
|
+
.command('graph-entity <entity>')
|
|
409
|
+
.description('Get all facts connected to an entity (as subject or object)')
|
|
410
|
+
.action((entity: string) => {
|
|
411
|
+
withDb((dbPath) => {
|
|
412
|
+
const db = openDb(dbPath);
|
|
413
|
+
initSchema(db);
|
|
414
|
+
const edges = getEntityGraph(db, entity);
|
|
415
|
+
console.log(JSON.stringify({ ok: true, entity, count: edges.length, edges }));
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
program
|
|
420
|
+
.command('graph-related <entity>')
|
|
421
|
+
.description('Get all entities directly connected to an entity')
|
|
422
|
+
.action((entity: string) => {
|
|
423
|
+
withDb((dbPath) => {
|
|
424
|
+
const db = openDb(dbPath);
|
|
425
|
+
initSchema(db);
|
|
426
|
+
const related = getRelatedEntities(db, entity);
|
|
427
|
+
console.log(JSON.stringify({ ok: true, entity, count: related.length, related }));
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
program
|
|
432
|
+
.command('graph-path <fromEntity> <toEntity>')
|
|
433
|
+
.description('Find paths between two entities in the knowledge graph')
|
|
434
|
+
.option('--max-depth <n>', 'Maximum path depth (default 4)', '4')
|
|
435
|
+
.option('--max-paths <n>', 'Maximum number of paths to return (default 5)', '5')
|
|
436
|
+
.action((fromEntity: string, toEntity: string, cmdOpts) => {
|
|
437
|
+
withDb((dbPath) => {
|
|
438
|
+
const db = openDb(dbPath);
|
|
439
|
+
initSchema(db);
|
|
440
|
+
const maxDepth = Math.max(1, Math.min(10, Number(cmdOpts.maxDepth ?? 4)));
|
|
441
|
+
const maxPaths = Math.max(1, Math.min(20, Number(cmdOpts.maxPaths ?? 5)));
|
|
442
|
+
const paths = findPaths(db, fromEntity, toEntity, maxDepth, maxPaths);
|
|
443
|
+
console.log(JSON.stringify({ ok: true, from: fromEntity, to: toEntity, count: paths.length, paths }));
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
program
|
|
448
|
+
.command('graph-export [outputFile]')
|
|
449
|
+
.description('Export the knowledge graph as JSON (for visualization)')
|
|
450
|
+
.option('--limit <n>', 'Max edges to export (default 1000)', '1000')
|
|
451
|
+
.option('--min-confidence <n>', 'Minimum confidence threshold (default 0)', '0')
|
|
452
|
+
.option('--entity <entity>', 'Export only subgraph around this entity')
|
|
453
|
+
.action((outputFile: string | undefined, cmdOpts) => {
|
|
454
|
+
withDb((dbPath) => {
|
|
455
|
+
const db = openDb(dbPath);
|
|
456
|
+
initSchema(db);
|
|
457
|
+
const graph = exportGraphJson(db, {
|
|
458
|
+
limit: Number(cmdOpts.limit ?? 1000),
|
|
459
|
+
minConfidence: Number(cmdOpts.minConfidence ?? 0),
|
|
460
|
+
entity: cmdOpts.entity,
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
const output = JSON.stringify({ ok: true, graph }, null, 2);
|
|
464
|
+
if (outputFile) {
|
|
465
|
+
fs.writeFileSync(outputFile, output);
|
|
466
|
+
console.log(JSON.stringify({ ok: true, file: outputFile, nodes: graph.nodes.length, edges: graph.edges.length }));
|
|
467
|
+
} else {
|
|
468
|
+
console.log(output);
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
program
|
|
474
|
+
.command('search-entities <pattern>')
|
|
475
|
+
.description('Search for entities matching a pattern')
|
|
476
|
+
.option('--limit <n>', 'Max results (default 50)', '50')
|
|
477
|
+
.action((pattern: string, cmdOpts) => {
|
|
478
|
+
withDb((dbPath) => {
|
|
479
|
+
const db = openDb(dbPath);
|
|
480
|
+
initSchema(db);
|
|
481
|
+
const limit = Math.max(1, Math.min(500, Number(cmdOpts.limit ?? 50)));
|
|
482
|
+
const entities = searchEntities(db, pattern, limit);
|
|
483
|
+
console.log(JSON.stringify({ ok: true, pattern, count: entities.length, entities }));
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
|
|
257
487
|
program.parse();
|