@raviolelabs/engram-mcp 0.2.2 → 0.4.2

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.
Files changed (64) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +139 -134
  3. package/SKILL.md +33 -0
  4. package/dist/core/db/index.d.ts.map +1 -1
  5. package/dist/core/db/index.js +27 -1
  6. package/dist/core/db/index.js.map +1 -1
  7. package/dist/core/logger.d.ts.map +1 -1
  8. package/dist/core/logger.js +4 -4
  9. package/dist/core/logger.js.map +1 -1
  10. package/dist/core/server/http.d.ts +3 -0
  11. package/dist/core/server/http.d.ts.map +1 -1
  12. package/dist/core/server/http.js +6 -0
  13. package/dist/core/server/http.js.map +1 -1
  14. package/dist/core/server/instructions.js +1 -1
  15. package/dist/core/server/mcp-handler.d.ts.map +1 -1
  16. package/dist/core/server/mcp-handler.js +28 -1
  17. package/dist/core/server/mcp-handler.js.map +1 -1
  18. package/dist/embeddings/index.d.ts +6 -0
  19. package/dist/embeddings/index.d.ts.map +1 -1
  20. package/dist/embeddings/index.js +31 -3
  21. package/dist/embeddings/index.js.map +1 -1
  22. package/dist/embeddings/providers/engram.d.ts +10 -0
  23. package/dist/embeddings/providers/engram.d.ts.map +1 -1
  24. package/dist/embeddings/providers/engram.js +35 -12
  25. package/dist/embeddings/providers/engram.js.map +1 -1
  26. package/dist/mcp-server/tests/mcp-e2e.test.js +1 -1
  27. package/dist/mcp-server/tests/mcp-e2e.test.js.map +1 -1
  28. package/dist/memory/core/store.d.ts.map +1 -1
  29. package/dist/memory/core/store.js +5 -4
  30. package/dist/memory/core/store.js.map +1 -1
  31. package/dist/memory/modules/team/keystore.d.ts +38 -0
  32. package/dist/memory/modules/team/keystore.d.ts.map +1 -0
  33. package/dist/memory/modules/team/keystore.js +117 -0
  34. package/dist/memory/modules/team/keystore.js.map +1 -0
  35. package/dist/memory/modules/team/tools.d.ts +10 -0
  36. package/dist/memory/modules/team/tools.d.ts.map +1 -0
  37. package/dist/memory/modules/team/tools.js +252 -0
  38. package/dist/memory/modules/team/tools.js.map +1 -0
  39. package/dist/memory/public/tools.d.ts.map +1 -1
  40. package/dist/memory/public/tools.js +33 -5
  41. package/dist/memory/public/tools.js.map +1 -1
  42. package/dist/server/api/memories.d.ts.map +1 -1
  43. package/dist/server/api/memories.js +79 -8
  44. package/dist/server/api/memories.js.map +1 -1
  45. package/dist/server/api/types.d.ts +5 -0
  46. package/dist/server/api/types.d.ts.map +1 -1
  47. package/dist/server/api/types.js +90 -2
  48. package/dist/server/api/types.js.map +1 -1
  49. package/dist/sync/team-sync.d.ts +19 -0
  50. package/dist/sync/team-sync.d.ts.map +1 -0
  51. package/dist/sync/team-sync.js +80 -0
  52. package/dist/sync/team-sync.js.map +1 -0
  53. package/dist/tests/scope-encryption.test.d.ts +2 -0
  54. package/dist/tests/scope-encryption.test.d.ts.map +1 -0
  55. package/dist/tests/scope-encryption.test.js +71 -0
  56. package/dist/tests/scope-encryption.test.js.map +1 -0
  57. package/dist/types.d.ts +5 -1
  58. package/dist/types.d.ts.map +1 -1
  59. package/dist/webapp/api/team.d.ts +7 -0
  60. package/dist/webapp/api/team.d.ts.map +1 -0
  61. package/dist/webapp/api/team.js +57 -0
  62. package/dist/webapp/api/team.js.map +1 -0
  63. package/package.json +2 -1
  64. package/CLAUDE.md +0 -232
@@ -0,0 +1,19 @@
1
+ import type { EngramConfig } from '../config/schema.js';
2
+ export interface WorkspaceSyncMessage {
3
+ type: 'workspace.memory_added' | 'workspace.memory_updated' | 'workspace.memory_deleted' | 'workspace.key_rotated';
4
+ workspace_id: string;
5
+ memory_id?: string;
6
+ ts: number;
7
+ }
8
+ /**
9
+ * Post a workspace memory op to engram-cloud so it fans out to all workspace members.
10
+ * Called by MemoryStore after insert/update/delete for workspace-scoped items.
11
+ * Non-fatal: logs warnings on failure.
12
+ */
13
+ export declare function broadcastWorkspaceOp(config: EngramConfig, jwt: string, msg: WorkspaceSyncMessage): Promise<void>;
14
+ /**
15
+ * Handle incoming workspace sync messages from the UserSyncChannel WebSocket.
16
+ * Called when the sync WS receives a 'workspace.*' message frame.
17
+ */
18
+ export declare function handleWorkspaceSyncMessage(msg: WorkspaceSyncMessage, store: import('../memory/core/store.js').MemoryStore, dataDir: string, masterKey: Uint8Array): Promise<void>;
19
+ //# sourceMappingURL=team-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team-sync.d.ts","sourceRoot":"","sources":["../../src/sync/team-sync.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAIxD,MAAM,WAAW,oBAAoB;IACnC,IAAI,EACA,wBAAwB,GACxB,0BAA0B,GAC1B,0BAA0B,GAC1B,uBAAuB,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,YAAY,EACpB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,oBAAoB,GACxB,OAAO,CAAC,IAAI,CAAC,CA0Bf;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAC9C,GAAG,EAAE,oBAAoB,EACzB,KAAK,EAAE,OAAO,yBAAyB,EAAE,WAAW,EACpD,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,UAAU,GACpB,OAAO,CAAC,IAAI,CAAC,CAuBf"}
@@ -0,0 +1,80 @@
1
+ // src/sync/team-sync.ts
2
+ // Handles incoming team/workspace sync messages from the UserSyncChannel WebSocket.
3
+ // Also provides broadcastTeamOp for pushing workspace ops to the cloud fan-out.
4
+ import { createLogger } from '../logger.js';
5
+ const log = createLogger('sync:team');
6
+ /**
7
+ * Post a workspace memory op to engram-cloud so it fans out to all workspace members.
8
+ * Called by MemoryStore after insert/update/delete for workspace-scoped items.
9
+ * Non-fatal: logs warnings on failure.
10
+ */
11
+ export async function broadcastWorkspaceOp(config, jwt, msg) {
12
+ const cloudBaseUrl = config.cloudBaseUrl
13
+ ?? 'https://api.engram-mcp.com';
14
+ try {
15
+ const res = await fetch(`${cloudBaseUrl}/api/workspaces/${msg.workspace_id}/broadcast`, {
16
+ method: 'POST',
17
+ headers: {
18
+ 'Content-Type': 'application/json',
19
+ Cookie: `engram_session=${jwt}`,
20
+ },
21
+ body: JSON.stringify(msg),
22
+ });
23
+ if (!res.ok) {
24
+ log.warn('Workspace broadcast failed', {
25
+ status: res.status,
26
+ workspace_id: msg.workspace_id,
27
+ });
28
+ }
29
+ }
30
+ catch (e) {
31
+ log.warn('Workspace broadcast error (non-fatal)', e);
32
+ }
33
+ }
34
+ /**
35
+ * Handle incoming workspace sync messages from the UserSyncChannel WebSocket.
36
+ * Called when the sync WS receives a 'workspace.*' message frame.
37
+ */
38
+ export async function handleWorkspaceSyncMessage(msg, store, dataDir, masterKey) {
39
+ switch (msg.type) {
40
+ case 'workspace.memory_added':
41
+ case 'workspace.memory_updated':
42
+ // Pull op will sync on next cloud pull — no-op here
43
+ log.debug('workspace.memory_added/updated — will sync on next pull', msg);
44
+ break;
45
+ case 'workspace.memory_deleted':
46
+ if (msg.memory_id) {
47
+ await store.delete(msg.memory_id);
48
+ log.info(`Deleted workspace memory ${msg.memory_id} per sync`);
49
+ }
50
+ break;
51
+ case 'workspace.key_rotated': {
52
+ // Client must re-fetch the workspace key from cloud on next sync pull
53
+ log.info(`Key rotated for workspace ${msg.workspace_id} — will re-fetch on next sync`);
54
+ // Trigger async key re-fetch (fire-and-forget)
55
+ void refreshWorkspaceKey(dataDir, masterKey, msg.workspace_id);
56
+ break;
57
+ }
58
+ }
59
+ }
60
+ /**
61
+ * Re-fetch a workspace's wrapped_team_key from cloud and re-store it locally.
62
+ * Called after receiving a workspace.key_rotated event.
63
+ */
64
+ async function refreshWorkspaceKey(dataDir, masterKey, workspaceId) {
65
+ try {
66
+ // Import keystore dynamically to avoid circular imports
67
+ const { getOrCreateX25519Keypair, unwrapAndStoreWorkspaceKey } = await import('../memory/modules/team/keystore.js');
68
+ const keypair = await getOrCreateX25519Keypair(dataDir, masterKey);
69
+ // We'd need jwt + cloudUrl here — this is a best-effort stub.
70
+ // In production, the full sync reconnect flow will pull the updated wrapped key
71
+ // via GET /api/workspaces after the channel reconnects.
72
+ log.info(`Workspace key refresh queued for ${workspaceId} — will pick up on next sync`);
73
+ void keypair; // suppress unused warning
74
+ void unwrapAndStoreWorkspaceKey; // suppress unused warning
75
+ }
76
+ catch (e) {
77
+ log.warn(`Failed to queue workspace key refresh for ${workspaceId}`, e);
78
+ }
79
+ }
80
+ //# sourceMappingURL=team-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team-sync.js","sourceRoot":"","sources":["../../src/sync/team-sync.ts"],"names":[],"mappings":"AAAA,wBAAwB;AACxB,oFAAoF;AACpF,gFAAgF;AAEhF,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,MAAM,GAAG,GAAG,YAAY,CAAC,WAAW,CAAC,CAAC;AAatC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,MAAoB,EACpB,GAAW,EACX,GAAyB;IAEzB,MAAM,YAAY,GACf,MAA6C,CAAC,YAAkC;WAC9E,4BAA4B,CAAC;IAElC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CACrB,GAAG,YAAY,mBAAmB,GAAG,CAAC,YAAY,YAAY,EAC9D;YACE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,MAAM,EAAE,kBAAkB,GAAG,EAAE;aAChC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC;SAC1B,CACF,CAAC;QACF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACZ,GAAG,CAAC,IAAI,CAAC,4BAA4B,EAAE;gBACrC,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,YAAY,EAAE,GAAG,CAAC,YAAY;aAC/B,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,GAAG,CAAC,IAAI,CAAC,uCAAuC,EAAE,CAAC,CAAC,CAAC;IACvD,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,GAAyB,EACzB,KAAoD,EACpD,OAAe,EACf,SAAqB;IAErB,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC;QACjB,KAAK,wBAAwB,CAAC;QAC9B,KAAK,0BAA0B;YAC7B,oDAAoD;YACpD,GAAG,CAAC,KAAK,CAAC,yDAAyD,EAAE,GAAG,CAAC,CAAC;YAC1E,MAAM;QAER,KAAK,0BAA0B;YAC7B,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;gBAClB,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;gBAClC,GAAG,CAAC,IAAI,CAAC,4BAA4B,GAAG,CAAC,SAAS,WAAW,CAAC,CAAC;YACjE,CAAC;YACD,MAAM;QAER,KAAK,uBAAuB,CAAC,CAAC,CAAC;YAC7B,sEAAsE;YACtE,GAAG,CAAC,IAAI,CAAC,6BAA6B,GAAG,CAAC,YAAY,+BAA+B,CAAC,CAAC;YACvF,+CAA+C;YAC/C,KAAK,mBAAmB,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,YAAY,CAAC,CAAC;YAC/D,MAAM;QACR,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,mBAAmB,CAChC,OAAe,EACf,SAAqB,EACrB,WAAmB;IAEnB,IAAI,CAAC;QACH,wDAAwD;QACxD,MAAM,EAAE,wBAAwB,EAAE,0BAA0B,EAAE,GAC5D,MAAM,MAAM,CAAC,oCAAoC,CAAC,CAAC;QAErD,MAAM,OAAO,GAAG,MAAM,wBAAwB,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;QAEnE,8DAA8D;QAC9D,gFAAgF;QAChF,wDAAwD;QACxD,GAAG,CAAC,IAAI,CAAC,oCAAoC,WAAW,8BAA8B,CAAC,CAAC;QACxF,KAAK,OAAO,CAAC,CAAC,0BAA0B;QACxC,KAAK,0BAA0B,CAAC,CAAC,0BAA0B;IAC7D,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,GAAG,CAAC,IAAI,CAAC,6CAA6C,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC;IAC1E,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=scope-encryption.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope-encryption.test.d.ts","sourceRoot":"","sources":["../../src/tests/scope-encryption.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,71 @@
1
+ // src/tests/scope-encryption.test.ts
2
+ // Verifies that workspace-scoped memories use a different encryption key
3
+ // (team master key) from personal memories (personal master key).
4
+ // Uses real libsodium — no mocks.
5
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
6
+ import sodium from 'libsodium-wrappers';
7
+ import fs from 'fs/promises';
8
+ import path from 'path';
9
+ import os from 'os';
10
+ import { generateWorkspaceKey, wrapKeyForRecipient, unwrapAndStoreWorkspaceKey, loadWorkspaceKey, getOrCreateX25519Keypair, } from '../memory/modules/team/keystore.js';
11
+ let tmpDir;
12
+ beforeAll(async () => {
13
+ await sodium.ready;
14
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'engram-scope-test-'));
15
+ });
16
+ afterAll(async () => {
17
+ await fs.rm(tmpDir, { recursive: true, force: true });
18
+ });
19
+ describe('scope encryption', () => {
20
+ it('generates a 32-byte workspace key', async () => {
21
+ const key = await generateWorkspaceKey();
22
+ expect(key).toBeInstanceOf(Uint8Array);
23
+ expect(key.length).toBe(32);
24
+ });
25
+ it('generates X25519 keypair and persists it', async () => {
26
+ const masterKey = sodium.randombytes_buf(32);
27
+ const kp = await getOrCreateX25519Keypair(tmpDir, masterKey);
28
+ expect(kp.publicKey.length).toBe(32);
29
+ expect(kp.privateKey.length).toBe(32);
30
+ // Second call returns the same keypair (loaded from disk)
31
+ const kp2 = await getOrCreateX25519Keypair(tmpDir, masterKey);
32
+ expect(sodium.to_hex(kp2.publicKey)).toBe(sodium.to_hex(kp.publicKey));
33
+ });
34
+ it('wraps team key for recipient and unwraps correctly', async () => {
35
+ // Use a fresh tmpDir so the prior test's persisted keypair doesn't interfere
36
+ const wrapDir = await fs.mkdtemp(path.join(os.tmpdir(), 'engram-wrap-test-'));
37
+ const masterKey = sodium.randombytes_buf(32);
38
+ const keypair = await getOrCreateX25519Keypair(wrapDir, masterKey);
39
+ const teamKey = await generateWorkspaceKey();
40
+ const pubkeyHex = sodium.to_hex(keypair.publicKey);
41
+ // Owner wraps team key for recipient
42
+ const wrappedHex = await wrapKeyForRecipient(teamKey, pubkeyHex);
43
+ expect(wrappedHex).toMatch(/^[0-9a-f]+$/);
44
+ // Recipient unwraps
45
+ const workspaceId = 'TEST01WORKSPACEID01';
46
+ const unwrapped = await unwrapAndStoreWorkspaceKey(wrapDir, workspaceId, wrappedHex, keypair);
47
+ expect(sodium.to_hex(unwrapped)).toBe(sodium.to_hex(teamKey));
48
+ // Persisted key matches
49
+ const loaded = await loadWorkspaceKey(wrapDir, workspaceId);
50
+ await fs.rm(wrapDir, { recursive: true, force: true });
51
+ expect(loaded).not.toBeNull();
52
+ expect(sodium.to_hex(loaded)).toBe(sodium.to_hex(teamKey));
53
+ });
54
+ it('team key is different from personal master key', async () => {
55
+ const masterKey = sodium.randombytes_buf(32);
56
+ const teamKey = await generateWorkspaceKey();
57
+ // Very high probability that two 32-byte random values differ
58
+ expect(sodium.to_hex(masterKey)).not.toBe(sodium.to_hex(teamKey));
59
+ });
60
+ it('wrapped keys for different recipients cannot be decrypted by wrong key', async () => {
61
+ const masterKey = sodium.randombytes_buf(32);
62
+ const keypairAlice = await getOrCreateX25519Keypair(tmpDir + '-alice', masterKey);
63
+ // Bob has a different key directory
64
+ const keypairBob = await getOrCreateX25519Keypair(tmpDir + '-bob', masterKey);
65
+ const teamKey = await generateWorkspaceKey();
66
+ const wrappedForAlice = await wrapKeyForRecipient(teamKey, sodium.to_hex(keypairAlice.publicKey));
67
+ // Bob tries to unwrap a key sealed for Alice — should throw
68
+ await expect(unwrapAndStoreWorkspaceKey(tmpDir + '-bob', 'WRONG_WORKSPACE_ID', wrappedForAlice, keypairBob)).rejects.toThrow();
69
+ });
70
+ });
71
+ //# sourceMappingURL=scope-encryption.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope-encryption.test.js","sourceRoot":"","sources":["../../src/tests/scope-encryption.test.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -120,7 +120,11 @@ export declare const MemoryItemSchema: z.ZodObject<{
120
120
  related_ids: string[];
121
121
  embedding_model: string;
122
122
  }>;
123
- export type MemoryItem = z.infer<typeof MemoryItemSchema>;
123
+ /** Runtime type for a memory item. scope is optional — defaults to 'personal'. */
124
+ export type MemoryItem = z.infer<typeof MemoryItemSchema> & {
125
+ /** Scope: 'personal' (default) or 'workspace:<ulid>' for team shared memories. */
126
+ scope?: string;
127
+ };
124
128
  export interface IngestInput {
125
129
  content: string;
126
130
  source_id: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWjC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAU3B,CAAC;AAEH,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,MAAM,WAAW,WAAW;IAE1B,OAAO,EAAE,MAAM,CAAC;IAEhB,SAAS,EAAE,MAAM,CAAC;IAElB,UAAU,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWjC,CAAC;AAEH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAU3B,CAAC;AAEH,kFAAkF;AAClF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,GAAG;IAC1D,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,WAAW,WAAW;IAE1B,OAAO,EAAE,MAAM,CAAC;IAEhB,SAAS,EAAE,MAAM,CAAC;IAElB,UAAU,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB"}
@@ -0,0 +1,7 @@
1
+ import { Router } from 'express';
2
+ export interface TeamRouterOptions {
3
+ dataDir: string;
4
+ masterKey: Uint8Array;
5
+ }
6
+ export declare function buildTeamRouter(opts: TeamRouterOptions): Router;
7
+ //# sourceMappingURL=team.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team.d.ts","sourceRoot":"","sources":["../../../src/webapp/api/team.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAUjC,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,UAAU,CAAC;CACvB;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAsD/D"}
@@ -0,0 +1,57 @@
1
+ // src/webapp/api/team.ts
2
+ // Local HTTP endpoints for workspace (team) key operations.
3
+ // Called by the engram-mcp.com browser UI to perform key wrapping
4
+ // (the browser cannot hold the master key — it lives in this local process).
5
+ import { Router } from 'express';
6
+ import { createLogger } from '../../logger.js';
7
+ import { loadWorkspaceKey, wrapKeyForRecipient, getOrCreateX25519Keypair, } from '../../memory/modules/team/keystore.js';
8
+ const log = createLogger('api:team');
9
+ export function buildTeamRouter(opts) {
10
+ const router = Router();
11
+ // POST /api/team/wrap-key
12
+ // Body: { workspace_id: string; recipient_pubkey: string } (hex)
13
+ // Returns: { wrapped_team_key: string } (hex)
14
+ // Used by engram-mcp.com to wrap the workspace master key before sending an invitation.
15
+ router.post('/wrap-key', async (req, res) => {
16
+ try {
17
+ const { workspace_id, recipient_pubkey } = req.body;
18
+ if (!workspace_id || !recipient_pubkey) {
19
+ return res.status(400).json({ error: 'workspace_id and recipient_pubkey required' });
20
+ }
21
+ if (!/^[0-9a-f]{64}$/i.test(recipient_pubkey)) {
22
+ return res.status(400).json({ error: 'recipient_pubkey must be 64 hex chars (32 bytes)' });
23
+ }
24
+ const teamKey = await loadWorkspaceKey(opts.dataDir, workspace_id);
25
+ if (!teamKey) {
26
+ return res.status(404).json({
27
+ error: 'workspace_key_not_found',
28
+ message: 'Local workspace key not found. Are you the owner?',
29
+ });
30
+ }
31
+ const wrappedHex = await wrapKeyForRecipient(teamKey, recipient_pubkey);
32
+ log.info(`Wrapped workspace key for workspace ${workspace_id}`);
33
+ return res.json({ wrapped_team_key: wrappedHex });
34
+ }
35
+ catch (e) {
36
+ log.error('wrap-key error', e);
37
+ return res.status(500).json({ error: 'internal_error' });
38
+ }
39
+ });
40
+ // GET /api/team/pubkey
41
+ // Returns the local X25519 public key (hex).
42
+ // Used for registration with engram-cloud after pairing.
43
+ router.get('/pubkey', async (_req, res) => {
44
+ try {
45
+ const keypair = await getOrCreateX25519Keypair(opts.dataDir, opts.masterKey);
46
+ const { default: sodium } = await import('libsodium-wrappers');
47
+ await sodium.ready;
48
+ return res.json({ x25519_pubkey: sodium.to_hex(keypair.publicKey) });
49
+ }
50
+ catch (e) {
51
+ log.error('pubkey error', e);
52
+ return res.status(500).json({ error: 'internal_error' });
53
+ }
54
+ });
55
+ return router;
56
+ }
57
+ //# sourceMappingURL=team.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team.js","sourceRoot":"","sources":["../../../src/webapp/api/team.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@raviolelabs/engram-mcp",
3
- "version": "0.2.2",
3
+ "version": "0.4.2",
4
+ "mcpName": "io.github.RavioleLabs/engram-mcp",
4
5
  "description": "EngramMCP — local-first semantic memory layer for AI agents",
5
6
  "license": "MIT",
6
7
  "type": "module",
package/CLAUDE.md DELETED
@@ -1,232 +0,0 @@
1
- # EngramMCP
2
-
3
- > Local-first semantic memory layer for AI agents.
4
-
5
- EngramMCP exposes a Model Context Protocol (MCP) server that lets any AI agent (Claude Code, Cursor, custom Claude/GPT runtimes) capture, search, and enrich memory across multiple typed sources (notes, conversations, Drive, Notion, YouTube, Obsidian, audio, plus user-defined custom types).
6
-
7
- Forked from Argos. Local-first: vectors + content live on the user's machine; embeddings via local Ollama by default; no cloud required for Phase 1.
8
-
9
- ---
10
-
11
- ## Instructions for Claude Code
12
-
13
- Tu es le stagiaire technique d'EngramMCP. Sois **proactif**, **force de proposition**, et **autonome**.
14
-
15
- ### Comportement attendu
16
-
17
- - **Lis le code existant** avant de modifier — comprends le pattern en place
18
- - **Respecte les conventions** (voir ci-dessous)
19
- - **Pense modularité** — chaque memory type est un MemoryModule indépendant
20
- - **Sois concis** — pas de blabla, va droit au but, montre le code
21
- - **Challenge les décisions** sous-optimales avec arguments
22
- - **No mocks** pour les intégrations externes — tests E2E avec Ollama/LanceDB/SQLite/API réels
23
-
24
- ### Quand tu travailles sur EngramMCP
25
-
26
- 1. Lis toujours le code existant avant de modifier
27
- 2. Respecte la convention ES modules avec extensions `.js` dans les imports
28
- 3. Property extraction est **off par défaut** — l'agent appelant (toi) fournit `title`/`tags` directement
29
- 4. Vector store dimension est hardcoded à 768 (nomic-embed-text). Voyage (1024)/OpenAI (1536) requiert un fix dans `src/vector/store.ts`
30
- 5. Tool descriptions doivent être agent-friendly (mention WHEN to call, WHAT inputs help retrieval)
31
- 6. **v0.2 surface**: 10 public tools only. Admin tools behind `--admin` flag. See SKILL.md for agent usage guide.
32
-
33
- ---
34
-
35
- ## Quick Reference
36
-
37
- ### Commands
38
-
39
- ```bash
40
- npm run dev # tsx watch src/scripts/serve.ts
41
- npm run build # tsc → dist/
42
- npm run build:client # Vite → src/client/dist/
43
- npm run build:all # client + server
44
- npm start # node dist/scripts/serve.js
45
- npm run reindex # re-embed all memories after provider change
46
- npm run install:wizard # interactive first-time setup (tsx)
47
- npm run pair # interactive pairing wizard (engram-mcp-pair)
48
- npm run rebuild # rebuild SQLite+LanceDB from ops_log (engram-mcp rebuild)
49
- npm test # vitest run
50
- npm run lint # eslint src/
51
- ```
52
-
53
- ### Tech Stack
54
-
55
- | Layer | Tech |
56
- |---|---|
57
- | Runtime | Node.js >= 22, TypeScript 5.7 strict, ESM |
58
- | MCP | `@modelcontextprotocol/sdk@1.28` (stdio + StreamableHTTP) |
59
- | Vector | LanceDB (`@lancedb/lancedb@0.27`) per-type tables |
60
- | SQL | better-sqlite3 + FTS5 |
61
- | Embeddings | OpenAI-compat dispatch → ollama / engram / voyage / openai / openai-compatible |
62
- | Audio | nodejs-whisper (whisper.cpp) |
63
- | YouTube | watch-page scrape + yt-dlp fallback |
64
- | Web | Express 5, ws, React 19, Vite 5, Tailwind v4, react-query |
65
- | Validation | Zod everywhere |
66
- | IDs | ULID |
67
-
68
- ### Key conventions
69
-
70
- - **Module pattern**: `"type": "module"`, imports with `.js` extension
71
- - **Config**: Zod schema in `src/config/schema.ts`, loaded from `~/.engram/config.json` or env (`ENGRAM_CONFIG_DIR`, `DATA_DIR`)
72
- - **DB**: `getDb()` singleton, prepared statements only
73
- - **Logging**: `createLogger(scope)` from `src/logger.ts`
74
- - **Tool descriptions**: agent-facing — explain WHEN to use the tool and what title/tags improve retrieval
75
- - **Tests**: real services (Ollama, LanceDB, Notion sandbox, etc.). No mocks. Real-API tests use ephemeral tmpdirs.
76
-
77
- ---
78
-
79
- ## Architecture (Phase 1)
80
-
81
- ```
82
- ┌─ stdio ─┐
83
- agent runtime ──┤ ├──▶ MCP server (engram-mcp)
84
- └─ HTTP ─┘ │
85
-
86
- ┌─ ToolRouter ─┐
87
- │ 10 tools │ (+18 admin with --admin flag)
88
- └──────┬───────┘
89
-
90
- ┌─ ModuleRegistry ─┐
91
- │ notes │
92
- │ conversations │
93
- │ drive (OAuth) │
94
- │ notion (OAuth) │
95
- │ youtube │
96
- │ audio (Whisper) │
97
- │ obsidian │
98
- │ <custom types> │
99
- └────────┬─────────┘
100
-
101
- ┌─ MemoryStore ─┐
102
- │ insert │ ──▶ chunk + embed (Ollama)
103
- │ search │ ──▶ LanceDB per-type tables
104
- │ getById │ ──▶ SQLite + FTS5
105
- │ delete │
106
- │ findRelated │
107
- │ setProperties│
108
- └────────┬──────┘
109
- │ events: memory.added/deleted/updated
110
-
111
- WebSocket /ws ──▶ dashboard React UI
112
- ```
113
-
114
- Cloud bits (Phase 2 — Plan K): `engram-mcp pair` CLI activates cloud transit poller (node-cron,
115
- 5 min), Bridge Relay WSS client, and E2E encrypted blob dispatch from the Engram cloud inbox.
116
- Set ENGRAM_PASSPHRASE env var for the transit poller to start automatically at server boot.
117
- Mobile app, billing, and Engram-hosted embeddings server are in separate Plans (I, J, M).
118
-
119
- Phase 3 — Plan N (ops-log sync): Every local write (insert/delete/setProperties) is logged as a
120
- signed, AES-256-GCM-encrypted op in `ops_log` (SQLite). A `ChannelClient` pushes pending ops to
121
- the cloud `UserSyncChannel` Durable Object (engram-cloud) and receives ops from other devices.
122
- The `ReplayApplier` decrypts, verifies ed25519 signatures, and applies ops with LWW + union
123
- conflict resolution. `engram-mcp rebuild` drops SQLite+LanceDB and replays all ops from scratch.
124
- Enable via `config.json: {"sync": {"enabled": true, "cloudBaseUrl": "..."}}` + ENGRAM_PASSPHRASE.
125
- Full multi-PC sync requires Plan O cloud worker deployed (see RUNBOOK.md).
126
-
127
- ---
128
-
129
- ## Project Structure
130
-
131
- ```
132
- src/
133
- ├── types.ts # MemoryItem schema (Zod) + interfaces
134
- ├── logger.ts # createLogger
135
- ├── index.ts # (stub — entry is src/scripts/serve.ts)
136
- ├── config/{schema,index}.ts # Zod config + loader
137
- ├── db/index.ts # SQLite init + 3 migrations
138
- ├── embeddings/
139
- │ ├── index.ts # dispatcher
140
- │ └── providers/{ollama,engram,voyage,openai,openai-compat}.ts
141
- ├── vector/store.ts # LanceDB per-type tables
142
- ├── memory/
143
- │ ├── core/{chunker,wikilinks,module-interface,module-registry,
144
- │ │ store,source-registry,property-extractor,reindex}.ts
145
- │ ├── modules/
146
- │ │ ├── notes/ # add_note, search_notes
147
- │ │ ├── conversations/ # remember_exchange, search_conversations
148
- │ │ ├── drive/ # OAuth + watcher + ingest/list/watch tools
149
- │ │ ├── notion/ # OAuth + watcher + ingest/list/watch tools
150
- │ │ ├── audio/ # Whisper.cpp + add_audio_file/search_audio
151
- │ │ ├── youtube/ # transcript fetcher + add_youtube_url/search
152
- │ │ ├── obsidian/ # vault reader + fs.watch + tools
153
- │ │ └── _custom/ # generic-module factory + create/list/delete
154
- │ ├── public/tools.ts # 10 public tools: remember, recall, get, update,
155
- │ │ # forget, relate, list_types, recent, ingest,
156
- │ │ # suggest_properties
157
- │ └── admin/tools.ts # ~18 admin tools behind --admin flag:
158
- ├── core/ # LOW-LEVEL UTILITIES (canonical)
159
- │ ├── logger.ts # createLogger (was src/logger.ts)
160
- │ ├── db/index.ts # SQLite init + migrations (was src/db/index.ts)
161
- │ └── server/
162
- │ ├── mcp-handler.ts # buildEngramRuntime + startStdioMcpServer
163
- │ ├── tool-router.ts # ToolRouter
164
- │ ├── http.ts # Express bootstrap (was src/webapp/server.ts)
165
- │ ├── mcp-http.ts # StreamableHTTP MCP transport
166
- │ └── websocket.ts # WS broadcaster for memory.* events
167
- ├── tools/ # Top-level tool registry (mirrors predmcp pattern)
168
- │ └── index.ts # registerAllTools + dynamic private import
169
- ├── private/ # GITIGNORED — premium extensions (hosted Engram only)
170
- │ ├── index.ts # registerPrivateExtensions (no-op placeholder)
171
- │ ├── README.md # Explains what goes here
172
- │ ├── tools/ # Premium MCP tools
173
- │ ├── algorithms/ # Advanced algorithm overrides
174
- │ └── prompts/ # Tuned LLM prompts (IP)
175
- ├── server/ # Engram-specific HTTP routes
176
- │ ├── index.ts # Re-exports from core/server/http.ts
177
- │ └── api/{memories,sources,types,views,daily,settings,reindex,sync-status,graph}.ts
178
- ├── mcp-server/ # Re-export shims (→ core/server/)
179
- │ ├── server.ts # shim → core/server/mcp-handler.ts
180
- │ └── tool-router.ts # shim → core/server/tool-router.ts
181
- ├── webapp/ # Re-export shims (→ core/server/)
182
- │ ├── server.ts # shim → core/server/http.ts
183
- │ ├── mcp-http.ts # shim → core/server/mcp-http.ts
184
- │ ├── websocket.ts # shim → core/server/websocket.ts
185
- │ └── api/ # original api handlers (not shimmed — used by tests)
186
- ├── client/ # React/Vite/Tailwind dashboard
187
- │ ├── vite.config.ts
188
- │ ├── tsconfig.json (separate)
189
- │ └── src/{App,main,api,ws,index.css}.{ts,tsx,css}
190
- └── scripts/{serve,install,install-ollama,reindex}.ts
191
- ```
192
-
193
- ---
194
-
195
- ## Database Schema
196
-
197
- SQLite at `<dataDir>/engram.db` (WAL mode, FOREIGN KEYS ON):
198
-
199
- | Table | Purpose |
200
- |---|---|
201
- | `memories` | id, type, source_id, content, content_hash, properties_json, wikilinks_json, related_ids_json, embedding_model, created_at |
202
- | `memories_fts` | FTS5 virtual table on content/title/tags |
203
- | `custom_types` | type_name, display_name, schema_json, created_at |
204
- | `oauth_tokens` | provider, access_token, refresh_token, expires_at, extra_json |
205
- | `module_state` | (module_id, key) → value_json |
206
- | `watched_sources` | id, module_id, external_id, display_name, config_json, last_synced_at, last_modified_remote, last_error, enabled |
207
- | `saved_views` | id, name, description, definition_json, pinned, created_at, updated_at |
208
- | `settings` | key → value_json |
209
-
210
- Vector tables: `memories_<type>` per memory type in LanceDB (id, source_id, chunk_index, content, created_at, field1-4, vector 768-d).
211
-
212
- ---
213
-
214
- ## Pricing (planned)
215
-
216
- - **Free**: full local — Ollama embeddings, all modules, ~36 MCP tools
217
- - **Pro $9/mo** (Phase 2): mobile app, cloud transit, share-by-email, Engram hosted embeddings, multi-PC encrypted sync, online dashboard
218
- - **BYO premium embeddings** at any tier: Voyage / OpenAI / OAI-compatible
219
-
220
- ---
221
-
222
- ## Phase status
223
-
224
- - [x] **Phase 1** — local MCP server with all memory types (DONE — see docs/superpowers/plans/)
225
- - [ ] **Phase 2** — Mobile + cloud transit + billing + hosted Engram embeddings server
226
- - [ ] **Phase 3** — Bidirectional multi-PC sync (ops-log + cloud saves) + recovery shards
227
-
228
- ---
229
-
230
- ## License
231
-
232
- MIT