@199-bio/engram 0.2.0 → 0.3.1
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/index.js +57 -13
- package/dist/storage/database.d.ts.map +1 -1
- package/dist/web/server.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/index.ts +61 -13
- package/src/storage/database.ts +130 -2
- package/src/web/server.ts +280 -0
- package/src/web/static/app.js +362 -0
- package/src/web/static/index.html +95 -0
- package/src/web/static/style.css +457 -0
package/dist/index.js
CHANGED
|
@@ -14,6 +14,7 @@ import { EngramDatabase } from "./storage/database.js";
|
|
|
14
14
|
import { KnowledgeGraph } from "./graph/knowledge-graph.js";
|
|
15
15
|
import { createRetriever } from "./retrieval/colbert.js";
|
|
16
16
|
import { HybridSearch } from "./retrieval/hybrid.js";
|
|
17
|
+
import { EngramWebServer } from "./web/server.js";
|
|
17
18
|
// ============ Configuration ============
|
|
18
19
|
const DB_PATH = process.env.ENGRAM_DB_PATH
|
|
19
20
|
? path.resolve(process.env.ENGRAM_DB_PATH.replace("~", os.homedir()))
|
|
@@ -23,6 +24,7 @@ const DB_FILE = path.join(DB_PATH, "engram.db");
|
|
|
23
24
|
let db;
|
|
24
25
|
let graph;
|
|
25
26
|
let search;
|
|
27
|
+
let webServer = null;
|
|
26
28
|
async function initialize() {
|
|
27
29
|
console.error(`[Engram] Initializing with database at ${DB_FILE}`);
|
|
28
30
|
db = new EngramDatabase(DB_FILE);
|
|
@@ -40,23 +42,24 @@ async function initialize() {
|
|
|
40
42
|
// ============ MCP Server ============
|
|
41
43
|
const server = new Server({
|
|
42
44
|
name: "engram",
|
|
43
|
-
version: "0.
|
|
45
|
+
version: "0.3.1",
|
|
44
46
|
}, {
|
|
45
47
|
capabilities: {
|
|
46
48
|
tools: {},
|
|
47
49
|
},
|
|
48
50
|
});
|
|
49
51
|
// Tool definitions with MCP 2025-06-18 annotations
|
|
52
|
+
// Descriptions are carefully written to guide Claude on when to use each tool
|
|
50
53
|
const TOOLS = [
|
|
51
54
|
{
|
|
52
55
|
name: "remember",
|
|
53
|
-
description: "
|
|
56
|
+
description: "PRIMARY STORAGE TOOL. Use this for ALL new information - conversations, facts, observations, notes. Automatically extracts people, organizations, and places as entities and creates relationships. Do NOT also call create_entity/observe/relate - remember handles entity extraction automatically. Only use remember once per piece of information.",
|
|
54
57
|
inputSchema: {
|
|
55
58
|
type: "object",
|
|
56
59
|
properties: {
|
|
57
60
|
content: {
|
|
58
61
|
type: "string",
|
|
59
|
-
description: "The
|
|
62
|
+
description: "The information to store - can be a conversation snippet, fact, observation, or note",
|
|
60
63
|
},
|
|
61
64
|
source: {
|
|
62
65
|
type: "string",
|
|
@@ -65,7 +68,7 @@ const TOOLS = [
|
|
|
65
68
|
},
|
|
66
69
|
importance: {
|
|
67
70
|
type: "number",
|
|
68
|
-
description: "Importance score from 0 to 1 (higher = more important)",
|
|
71
|
+
description: "Importance score from 0 to 1 (higher = more important). Use 0.7+ for key facts, 0.3- for casual mentions",
|
|
69
72
|
minimum: 0,
|
|
70
73
|
maximum: 1,
|
|
71
74
|
default: 0.5,
|
|
@@ -83,7 +86,7 @@ const TOOLS = [
|
|
|
83
86
|
},
|
|
84
87
|
{
|
|
85
88
|
name: "recall",
|
|
86
|
-
description: "
|
|
89
|
+
description: "PRIMARY SEARCH TOOL. Use this FIRST when answering any question about stored information. Searches across all memories using semantic understanding, keywords, and knowledge graph connections. Returns relevant memories with context.",
|
|
87
90
|
inputSchema: {
|
|
88
91
|
type: "object",
|
|
89
92
|
properties: {
|
|
@@ -114,7 +117,7 @@ const TOOLS = [
|
|
|
114
117
|
},
|
|
115
118
|
{
|
|
116
119
|
name: "forget",
|
|
117
|
-
description: "
|
|
120
|
+
description: "Delete a specific memory by its ID. Use only when explicitly asked to remove information.",
|
|
118
121
|
inputSchema: {
|
|
119
122
|
type: "object",
|
|
120
123
|
properties: {
|
|
@@ -135,13 +138,13 @@ const TOOLS = [
|
|
|
135
138
|
},
|
|
136
139
|
{
|
|
137
140
|
name: "create_entity",
|
|
138
|
-
description: "
|
|
141
|
+
description: "ADVANCED: Manually create an entity. Rarely needed - remember auto-extracts entities. Only use when: (1) creating an entity that won't appear in any memory content, or (2) correcting entity type after auto-extraction.",
|
|
139
142
|
inputSchema: {
|
|
140
143
|
type: "object",
|
|
141
144
|
properties: {
|
|
142
145
|
name: {
|
|
143
146
|
type: "string",
|
|
144
|
-
description: "Entity name (e.g., 'John', 'Paris', 'Machine Learning')",
|
|
147
|
+
description: "Entity name (e.g., 'John Smith', 'Paris', 'Machine Learning')",
|
|
145
148
|
},
|
|
146
149
|
type: {
|
|
147
150
|
type: "string",
|
|
@@ -161,13 +164,13 @@ const TOOLS = [
|
|
|
161
164
|
},
|
|
162
165
|
{
|
|
163
166
|
name: "observe",
|
|
164
|
-
description: "Add an
|
|
167
|
+
description: "ADVANCED: Add a fact to an EXISTING entity without creating a memory. Rarely needed - use remember instead, which stores the content AND links it to entities. Only use observe for adding metadata or corrections to entities.",
|
|
165
168
|
inputSchema: {
|
|
166
169
|
type: "object",
|
|
167
170
|
properties: {
|
|
168
171
|
entity: {
|
|
169
172
|
type: "string",
|
|
170
|
-
description: "Entity name to add observation to",
|
|
173
|
+
description: "Entity name to add observation to (must already exist)",
|
|
171
174
|
},
|
|
172
175
|
observation: {
|
|
173
176
|
type: "string",
|
|
@@ -191,7 +194,7 @@ const TOOLS = [
|
|
|
191
194
|
},
|
|
192
195
|
{
|
|
193
196
|
name: "relate",
|
|
194
|
-
description: "
|
|
197
|
+
description: "ADVANCED: Manually create a relationship between two entities. Rarely needed - remember auto-extracts relationships from text. Only use when: (1) the relationship wasn't captured by auto-extraction, or (2) you need to add a relationship not mentioned in any memory.",
|
|
195
198
|
inputSchema: {
|
|
196
199
|
type: "object",
|
|
197
200
|
properties: {
|
|
@@ -220,7 +223,7 @@ const TOOLS = [
|
|
|
220
223
|
},
|
|
221
224
|
{
|
|
222
225
|
name: "query_entity",
|
|
223
|
-
description: "Get
|
|
226
|
+
description: "Get all stored information about a specific person, place, or organization. Use after recall to get deeper details about an entity mentioned in search results.",
|
|
224
227
|
inputSchema: {
|
|
225
228
|
type: "object",
|
|
226
229
|
properties: {
|
|
@@ -241,7 +244,7 @@ const TOOLS = [
|
|
|
241
244
|
},
|
|
242
245
|
{
|
|
243
246
|
name: "list_entities",
|
|
244
|
-
description: "List all entities,
|
|
247
|
+
description: "List all known entities (people, places, organizations, etc.). Use to browse the knowledge graph or find entity names for query_entity.",
|
|
245
248
|
inputSchema: {
|
|
246
249
|
type: "object",
|
|
247
250
|
properties: {
|
|
@@ -280,6 +283,27 @@ const TOOLS = [
|
|
|
280
283
|
openWorldHint: false,
|
|
281
284
|
},
|
|
282
285
|
},
|
|
286
|
+
{
|
|
287
|
+
name: "engram_web",
|
|
288
|
+
description: "Launch the Engram web interface for browsing, searching, and editing memories visually. Returns a URL to open in your browser.",
|
|
289
|
+
inputSchema: {
|
|
290
|
+
type: "object",
|
|
291
|
+
properties: {
|
|
292
|
+
port: {
|
|
293
|
+
type: "number",
|
|
294
|
+
description: "Port to run the web server on (default: 3847)",
|
|
295
|
+
default: 3847,
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
annotations: {
|
|
300
|
+
title: "Launch Web Interface",
|
|
301
|
+
readOnlyHint: false,
|
|
302
|
+
destructiveHint: false,
|
|
303
|
+
idempotentHint: true,
|
|
304
|
+
openWorldHint: true,
|
|
305
|
+
},
|
|
306
|
+
},
|
|
283
307
|
];
|
|
284
308
|
// List available tools
|
|
285
309
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
@@ -505,6 +529,26 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
505
529
|
],
|
|
506
530
|
};
|
|
507
531
|
}
|
|
532
|
+
case "engram_web": {
|
|
533
|
+
const { port = 3847 } = args;
|
|
534
|
+
// Create or reuse web server
|
|
535
|
+
if (!webServer) {
|
|
536
|
+
webServer = new EngramWebServer({ db, graph, search, port });
|
|
537
|
+
}
|
|
538
|
+
const url = await webServer.start();
|
|
539
|
+
return {
|
|
540
|
+
content: [
|
|
541
|
+
{
|
|
542
|
+
type: "text",
|
|
543
|
+
text: JSON.stringify({
|
|
544
|
+
success: true,
|
|
545
|
+
url,
|
|
546
|
+
message: `Web interface running at ${url}`,
|
|
547
|
+
}, null, 2),
|
|
548
|
+
},
|
|
549
|
+
],
|
|
550
|
+
};
|
|
551
|
+
}
|
|
508
552
|
default:
|
|
509
553
|
throw new Error(`Unknown tool: ${name}`);
|
|
510
554
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/storage/database.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,IAAI,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,cAAc,CAAC;IAChE,UAAU,EAAE,IAAI,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,IAAI,CAAC;IACjB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC3C,UAAU,EAAE,IAAI,CAAC;CAClB;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,SAAS,CAA8C;gBAEnD,MAAM,EAAE,MAAM;IAoB1B,OAAO,CAAC,UAAU;IAyFlB,YAAY,CACV,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAuB,EAC/B,UAAU,GAAE,MAAY,GACvB,MAAM;IAUT,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAKpC,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG,YAAY,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI;IAqBjG,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAMjC,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAI7B,cAAc,CAAC,KAAK,GAAE,MAAa,GAAG,MAAM,EAAE;IAO9C,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,KAAK,CAAC,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAkBhF,OAAO,CAAC,eAAe;IAavB,YAAY,CACV,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,EACpB,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAW,GAC9C,MAAM;IAUT,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAKpC,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/storage/database.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAiBtD,UAAU,gBAAgB;IACxB,EAAE,EAAE,cAAc,CAAC;IACnB,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,YAAY,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,EAAE,CAAiB;IAC3B,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,IAAI,CAAS;gBAET,OAAO,EAAE,gBAAgB;IAO/B,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAkB9B,IAAI,IAAI,IAAI;YAOE,aAAa;YA+Bb,SAAS;YAgIT,WAAW;IAgCzB,OAAO,CAAC,SAAS;CAclB"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { EngramDatabase } from "./storage/database.js";
|
|
|
19
19
|
import { KnowledgeGraph } from "./graph/knowledge-graph.js";
|
|
20
20
|
import { createRetriever } from "./retrieval/colbert.js";
|
|
21
21
|
import { HybridSearch } from "./retrieval/hybrid.js";
|
|
22
|
+
import { EngramWebServer } from "./web/server.js";
|
|
22
23
|
|
|
23
24
|
// ============ Configuration ============
|
|
24
25
|
|
|
@@ -33,6 +34,7 @@ const DB_FILE = path.join(DB_PATH, "engram.db");
|
|
|
33
34
|
let db: EngramDatabase;
|
|
34
35
|
let graph: KnowledgeGraph;
|
|
35
36
|
let search: HybridSearch;
|
|
37
|
+
let webServer: EngramWebServer | null = null;
|
|
36
38
|
|
|
37
39
|
async function initialize(): Promise<void> {
|
|
38
40
|
console.error(`[Engram] Initializing with database at ${DB_FILE}`);
|
|
@@ -58,7 +60,7 @@ async function initialize(): Promise<void> {
|
|
|
58
60
|
const server = new Server(
|
|
59
61
|
{
|
|
60
62
|
name: "engram",
|
|
61
|
-
version: "0.
|
|
63
|
+
version: "0.3.1",
|
|
62
64
|
},
|
|
63
65
|
{
|
|
64
66
|
capabilities: {
|
|
@@ -68,17 +70,18 @@ const server = new Server(
|
|
|
68
70
|
);
|
|
69
71
|
|
|
70
72
|
// Tool definitions with MCP 2025-06-18 annotations
|
|
73
|
+
// Descriptions are carefully written to guide Claude on when to use each tool
|
|
71
74
|
const TOOLS = [
|
|
72
75
|
{
|
|
73
76
|
name: "remember",
|
|
74
77
|
description:
|
|
75
|
-
"
|
|
78
|
+
"PRIMARY STORAGE TOOL. Use this for ALL new information - conversations, facts, observations, notes. Automatically extracts people, organizations, and places as entities and creates relationships. Do NOT also call create_entity/observe/relate - remember handles entity extraction automatically. Only use remember once per piece of information.",
|
|
76
79
|
inputSchema: {
|
|
77
80
|
type: "object" as const,
|
|
78
81
|
properties: {
|
|
79
82
|
content: {
|
|
80
83
|
type: "string",
|
|
81
|
-
description: "The
|
|
84
|
+
description: "The information to store - can be a conversation snippet, fact, observation, or note",
|
|
82
85
|
},
|
|
83
86
|
source: {
|
|
84
87
|
type: "string",
|
|
@@ -87,7 +90,7 @@ const TOOLS = [
|
|
|
87
90
|
},
|
|
88
91
|
importance: {
|
|
89
92
|
type: "number",
|
|
90
|
-
description: "Importance score from 0 to 1 (higher = more important)",
|
|
93
|
+
description: "Importance score from 0 to 1 (higher = more important). Use 0.7+ for key facts, 0.3- for casual mentions",
|
|
91
94
|
minimum: 0,
|
|
92
95
|
maximum: 1,
|
|
93
96
|
default: 0.5,
|
|
@@ -106,7 +109,7 @@ const TOOLS = [
|
|
|
106
109
|
{
|
|
107
110
|
name: "recall",
|
|
108
111
|
description:
|
|
109
|
-
"
|
|
112
|
+
"PRIMARY SEARCH TOOL. Use this FIRST when answering any question about stored information. Searches across all memories using semantic understanding, keywords, and knowledge graph connections. Returns relevant memories with context.",
|
|
110
113
|
inputSchema: {
|
|
111
114
|
type: "object" as const,
|
|
112
115
|
properties: {
|
|
@@ -137,7 +140,7 @@ const TOOLS = [
|
|
|
137
140
|
},
|
|
138
141
|
{
|
|
139
142
|
name: "forget",
|
|
140
|
-
description: "
|
|
143
|
+
description: "Delete a specific memory by its ID. Use only when explicitly asked to remove information.",
|
|
141
144
|
inputSchema: {
|
|
142
145
|
type: "object" as const,
|
|
143
146
|
properties: {
|
|
@@ -158,13 +161,13 @@ const TOOLS = [
|
|
|
158
161
|
},
|
|
159
162
|
{
|
|
160
163
|
name: "create_entity",
|
|
161
|
-
description: "
|
|
164
|
+
description: "ADVANCED: Manually create an entity. Rarely needed - remember auto-extracts entities. Only use when: (1) creating an entity that won't appear in any memory content, or (2) correcting entity type after auto-extraction.",
|
|
162
165
|
inputSchema: {
|
|
163
166
|
type: "object" as const,
|
|
164
167
|
properties: {
|
|
165
168
|
name: {
|
|
166
169
|
type: "string",
|
|
167
|
-
description: "Entity name (e.g., 'John', 'Paris', 'Machine Learning')",
|
|
170
|
+
description: "Entity name (e.g., 'John Smith', 'Paris', 'Machine Learning')",
|
|
168
171
|
},
|
|
169
172
|
type: {
|
|
170
173
|
type: "string",
|
|
@@ -184,13 +187,13 @@ const TOOLS = [
|
|
|
184
187
|
},
|
|
185
188
|
{
|
|
186
189
|
name: "observe",
|
|
187
|
-
description: "Add an
|
|
190
|
+
description: "ADVANCED: Add a fact to an EXISTING entity without creating a memory. Rarely needed - use remember instead, which stores the content AND links it to entities. Only use observe for adding metadata or corrections to entities.",
|
|
188
191
|
inputSchema: {
|
|
189
192
|
type: "object" as const,
|
|
190
193
|
properties: {
|
|
191
194
|
entity: {
|
|
192
195
|
type: "string",
|
|
193
|
-
description: "Entity name to add observation to",
|
|
196
|
+
description: "Entity name to add observation to (must already exist)",
|
|
194
197
|
},
|
|
195
198
|
observation: {
|
|
196
199
|
type: "string",
|
|
@@ -214,7 +217,7 @@ const TOOLS = [
|
|
|
214
217
|
},
|
|
215
218
|
{
|
|
216
219
|
name: "relate",
|
|
217
|
-
description: "
|
|
220
|
+
description: "ADVANCED: Manually create a relationship between two entities. Rarely needed - remember auto-extracts relationships from text. Only use when: (1) the relationship wasn't captured by auto-extraction, or (2) you need to add a relationship not mentioned in any memory.",
|
|
218
221
|
inputSchema: {
|
|
219
222
|
type: "object" as const,
|
|
220
223
|
properties: {
|
|
@@ -243,7 +246,7 @@ const TOOLS = [
|
|
|
243
246
|
},
|
|
244
247
|
{
|
|
245
248
|
name: "query_entity",
|
|
246
|
-
description: "Get
|
|
249
|
+
description: "Get all stored information about a specific person, place, or organization. Use after recall to get deeper details about an entity mentioned in search results.",
|
|
247
250
|
inputSchema: {
|
|
248
251
|
type: "object" as const,
|
|
249
252
|
properties: {
|
|
@@ -264,7 +267,7 @@ const TOOLS = [
|
|
|
264
267
|
},
|
|
265
268
|
{
|
|
266
269
|
name: "list_entities",
|
|
267
|
-
description: "List all entities,
|
|
270
|
+
description: "List all known entities (people, places, organizations, etc.). Use to browse the knowledge graph or find entity names for query_entity.",
|
|
268
271
|
inputSchema: {
|
|
269
272
|
type: "object" as const,
|
|
270
273
|
properties: {
|
|
@@ -303,6 +306,27 @@ const TOOLS = [
|
|
|
303
306
|
openWorldHint: false,
|
|
304
307
|
},
|
|
305
308
|
},
|
|
309
|
+
{
|
|
310
|
+
name: "engram_web",
|
|
311
|
+
description: "Launch the Engram web interface for browsing, searching, and editing memories visually. Returns a URL to open in your browser.",
|
|
312
|
+
inputSchema: {
|
|
313
|
+
type: "object" as const,
|
|
314
|
+
properties: {
|
|
315
|
+
port: {
|
|
316
|
+
type: "number",
|
|
317
|
+
description: "Port to run the web server on (default: 3847)",
|
|
318
|
+
default: 3847,
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
annotations: {
|
|
323
|
+
title: "Launch Web Interface",
|
|
324
|
+
readOnlyHint: false,
|
|
325
|
+
destructiveHint: false,
|
|
326
|
+
idempotentHint: true,
|
|
327
|
+
openWorldHint: true,
|
|
328
|
+
},
|
|
329
|
+
},
|
|
306
330
|
];
|
|
307
331
|
|
|
308
332
|
// List available tools
|
|
@@ -587,6 +611,30 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
587
611
|
};
|
|
588
612
|
}
|
|
589
613
|
|
|
614
|
+
case "engram_web": {
|
|
615
|
+
const { port = 3847 } = args as { port?: number };
|
|
616
|
+
|
|
617
|
+
// Create or reuse web server
|
|
618
|
+
if (!webServer) {
|
|
619
|
+
webServer = new EngramWebServer({ db, graph, search, port });
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const url = await webServer.start();
|
|
623
|
+
|
|
624
|
+
return {
|
|
625
|
+
content: [
|
|
626
|
+
{
|
|
627
|
+
type: "text" as const,
|
|
628
|
+
text: JSON.stringify({
|
|
629
|
+
success: true,
|
|
630
|
+
url,
|
|
631
|
+
message: `Web interface running at ${url}`,
|
|
632
|
+
}, null, 2),
|
|
633
|
+
},
|
|
634
|
+
],
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
590
638
|
default:
|
|
591
639
|
throw new Error(`Unknown tool: ${name}`);
|
|
592
640
|
}
|
package/src/storage/database.ts
CHANGED
|
@@ -266,12 +266,140 @@ export class EngramDatabase {
|
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
findEntityByName(name: string): Entity | null {
|
|
269
|
+
// First try exact match (case-insensitive)
|
|
269
270
|
const row = this.stmt("SELECT * FROM entities WHERE LOWER(name) = LOWER(?)").get(name) as Record<string, unknown> | undefined;
|
|
270
|
-
|
|
271
|
+
if (row) return this.rowToEntity(row);
|
|
272
|
+
|
|
273
|
+
// If no exact match, try fuzzy match for potential duplicates
|
|
274
|
+
const fuzzyMatch = this.findSimilarEntity(name);
|
|
275
|
+
return fuzzyMatch;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Find a similar entity using fuzzy matching
|
|
280
|
+
* Catches "Boris D" matching "Boris Djordjevic", "John" matching "John Smith"
|
|
281
|
+
*/
|
|
282
|
+
findSimilarEntity(name: string, threshold: number = 0.8): Entity | null {
|
|
283
|
+
const normalizedName = name.toLowerCase().trim();
|
|
284
|
+
const nameWords = normalizedName.split(/\s+/);
|
|
285
|
+
|
|
286
|
+
// Get candidates: entities that share at least one word with the query
|
|
287
|
+
const candidates = this.stmt(`
|
|
288
|
+
SELECT * FROM entities
|
|
289
|
+
WHERE LOWER(name) LIKE ?
|
|
290
|
+
LIMIT 100
|
|
291
|
+
`).all(`%${nameWords[0]}%`) as Record<string, unknown>[];
|
|
292
|
+
|
|
293
|
+
let bestMatch: Entity | null = null;
|
|
294
|
+
let bestScore = 0;
|
|
295
|
+
|
|
296
|
+
for (const row of candidates) {
|
|
297
|
+
const entity = this.rowToEntity(row);
|
|
298
|
+
const entityName = entity.name.toLowerCase();
|
|
299
|
+
const entityWords = entityName.split(/\s+/);
|
|
300
|
+
|
|
301
|
+
// Calculate similarity score
|
|
302
|
+
const score = this.calculateNameSimilarity(nameWords, entityWords, normalizedName, entityName);
|
|
303
|
+
|
|
304
|
+
if (score >= threshold && score > bestScore) {
|
|
305
|
+
bestScore = score;
|
|
306
|
+
bestMatch = entity;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return bestMatch;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Calculate similarity between two names
|
|
315
|
+
* Returns 0-1 score (1 = identical)
|
|
316
|
+
*/
|
|
317
|
+
private calculateNameSimilarity(
|
|
318
|
+
words1: string[],
|
|
319
|
+
words2: string[],
|
|
320
|
+
full1: string,
|
|
321
|
+
full2: string
|
|
322
|
+
): number {
|
|
323
|
+
// Exact match
|
|
324
|
+
if (full1 === full2) return 1.0;
|
|
325
|
+
|
|
326
|
+
// One is prefix of the other (e.g., "Boris" vs "Boris Djordjevic")
|
|
327
|
+
if (full1.startsWith(full2 + " ") || full2.startsWith(full1 + " ")) {
|
|
328
|
+
return 0.9;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// First word matches (e.g., "John" vs "John Smith")
|
|
332
|
+
if (words1[0] === words2[0]) {
|
|
333
|
+
// Same first word, different lengths
|
|
334
|
+
const longer = Math.max(words1.length, words2.length);
|
|
335
|
+
const shorter = Math.min(words1.length, words2.length);
|
|
336
|
+
return 0.7 + (0.2 * shorter / longer);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Check for abbreviated names (e.g., "Boris D" vs "Boris Djordjevic")
|
|
340
|
+
if (words1.length >= 2 && words2.length >= 2) {
|
|
341
|
+
const last1 = words1[words1.length - 1];
|
|
342
|
+
const last2 = words2[words2.length - 1];
|
|
343
|
+
|
|
344
|
+
// Check if one is abbreviation of the other
|
|
345
|
+
if (last1.length === 1 && last2.startsWith(last1)) {
|
|
346
|
+
return 0.85;
|
|
347
|
+
}
|
|
348
|
+
if (last2.length === 1 && last1.startsWith(last2)) {
|
|
349
|
+
return 0.85;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Count shared words
|
|
354
|
+
const set1 = new Set(words1);
|
|
355
|
+
const shared = words2.filter(w => set1.has(w)).length;
|
|
356
|
+
const total = Math.max(words1.length, words2.length);
|
|
357
|
+
|
|
358
|
+
return shared / total * 0.7;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Find all potential duplicate entities
|
|
363
|
+
*/
|
|
364
|
+
findDuplicateEntities(): Array<{ entity: Entity; potentialDuplicates: Entity[] }> {
|
|
365
|
+
const entities = this.listEntities(undefined, 1000);
|
|
366
|
+
const duplicates: Array<{ entity: Entity; potentialDuplicates: Entity[] }> = [];
|
|
367
|
+
const processed = new Set<string>();
|
|
368
|
+
|
|
369
|
+
for (const entity of entities) {
|
|
370
|
+
if (processed.has(entity.id)) continue;
|
|
371
|
+
|
|
372
|
+
const potentialDupes: Entity[] = [];
|
|
373
|
+
const words = entity.name.toLowerCase().split(/\s+/);
|
|
374
|
+
|
|
375
|
+
for (const other of entities) {
|
|
376
|
+
if (other.id === entity.id || processed.has(other.id)) continue;
|
|
377
|
+
|
|
378
|
+
const otherWords = other.name.toLowerCase().split(/\s+/);
|
|
379
|
+
const score = this.calculateNameSimilarity(
|
|
380
|
+
words, otherWords,
|
|
381
|
+
entity.name.toLowerCase(),
|
|
382
|
+
other.name.toLowerCase()
|
|
383
|
+
);
|
|
384
|
+
|
|
385
|
+
if (score >= 0.8) {
|
|
386
|
+
potentialDupes.push(other);
|
|
387
|
+
processed.add(other.id);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (potentialDupes.length > 0) {
|
|
392
|
+
duplicates.push({ entity, potentialDuplicates: potentialDupes });
|
|
393
|
+
processed.add(entity.id);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return duplicates;
|
|
271
398
|
}
|
|
272
399
|
|
|
273
400
|
searchEntities(query: string, type?: Entity["type"]): Entity[] {
|
|
274
|
-
|
|
401
|
+
// Case-insensitive search
|
|
402
|
+
let sql = "SELECT * FROM entities WHERE LOWER(name) LIKE LOWER(?)";
|
|
275
403
|
const params: unknown[] = [`%${query}%`];
|
|
276
404
|
|
|
277
405
|
if (type) {
|