@raviolelabs/engram-mcp 0.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/CLAUDE.md +232 -0
- package/LICENSE +21 -0
- package/README.md +222 -0
- package/SKILL.md +299 -0
- package/dist/cloud/auth.d.ts +29 -0
- package/dist/cloud/auth.d.ts.map +1 -0
- package/dist/cloud/auth.js +132 -0
- package/dist/cloud/auth.js.map +1 -0
- package/dist/cloud/bridge-client.d.ts +10 -0
- package/dist/cloud/bridge-client.d.ts.map +1 -0
- package/dist/cloud/bridge-client.js +167 -0
- package/dist/cloud/bridge-client.js.map +1 -0
- package/dist/cloud/crypto.d.ts +42 -0
- package/dist/cloud/crypto.d.ts.map +1 -0
- package/dist/cloud/crypto.js +146 -0
- package/dist/cloud/crypto.js.map +1 -0
- package/dist/cloud/endpoints.d.ts +26 -0
- package/dist/cloud/endpoints.d.ts.map +1 -0
- package/dist/cloud/endpoints.js +26 -0
- package/dist/cloud/endpoints.js.map +1 -0
- package/dist/cloud/pairing.d.ts +30 -0
- package/dist/cloud/pairing.d.ts.map +1 -0
- package/dist/cloud/pairing.js +157 -0
- package/dist/cloud/pairing.js.map +1 -0
- package/dist/cloud/transit-poller.d.ts +35 -0
- package/dist/cloud/transit-poller.d.ts.map +1 -0
- package/dist/cloud/transit-poller.js +281 -0
- package/dist/cloud/transit-poller.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +24 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/schema.d.ts +466 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +171 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/core/db/index.d.ts +7 -0
- package/dist/core/db/index.d.ts.map +1 -0
- package/dist/core/db/index.js +273 -0
- package/dist/core/db/index.js.map +1 -0
- package/dist/core/logger.d.ts +19 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +223 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/server/http.d.ts +15 -0
- package/dist/core/server/http.d.ts.map +1 -0
- package/dist/core/server/http.js +76 -0
- package/dist/core/server/http.js.map +1 -0
- package/dist/core/server/instructions.d.ts +2 -0
- package/dist/core/server/instructions.d.ts.map +1 -0
- package/dist/core/server/instructions.js +36 -0
- package/dist/core/server/instructions.js.map +1 -0
- package/dist/core/server/mcp-handler.d.ts +39 -0
- package/dist/core/server/mcp-handler.d.ts.map +1 -0
- package/dist/core/server/mcp-handler.js +204 -0
- package/dist/core/server/mcp-handler.js.map +1 -0
- package/dist/core/server/mcp-http.d.ts +4 -0
- package/dist/core/server/mcp-http.d.ts.map +1 -0
- package/dist/core/server/mcp-http.js +56 -0
- package/dist/core/server/mcp-http.js.map +1 -0
- package/dist/core/server/tool-router.d.ts +9 -0
- package/dist/core/server/tool-router.d.ts.map +1 -0
- package/dist/core/server/tool-router.js +25 -0
- package/dist/core/server/tool-router.js.map +1 -0
- package/dist/core/server/websocket.d.ts +4 -0
- package/dist/core/server/websocket.d.ts.map +1 -0
- package/dist/core/server/websocket.js +25 -0
- package/dist/core/server/websocket.js.map +1 -0
- package/dist/db/index.d.ts +2 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +3 -0
- package/dist/db/index.js.map +1 -0
- package/dist/embeddings/index.d.ts +24 -0
- package/dist/embeddings/index.d.ts.map +1 -0
- package/dist/embeddings/index.js +86 -0
- package/dist/embeddings/index.js.map +1 -0
- package/dist/embeddings/providers/engram.d.ts +7 -0
- package/dist/embeddings/providers/engram.d.ts.map +1 -0
- package/dist/embeddings/providers/engram.js +67 -0
- package/dist/embeddings/providers/engram.js.map +1 -0
- package/dist/embeddings/providers/ollama.d.ts +3 -0
- package/dist/embeddings/providers/ollama.d.ts.map +1 -0
- package/dist/embeddings/providers/ollama.js +9 -0
- package/dist/embeddings/providers/ollama.js.map +1 -0
- package/dist/embeddings/providers/openai-compat.d.ts +7 -0
- package/dist/embeddings/providers/openai-compat.d.ts.map +1 -0
- package/dist/embeddings/providers/openai-compat.js +27 -0
- package/dist/embeddings/providers/openai-compat.js.map +1 -0
- package/dist/embeddings/providers/openai.d.ts +3 -0
- package/dist/embeddings/providers/openai.d.ts.map +1 -0
- package/dist/embeddings/providers/openai.js +12 -0
- package/dist/embeddings/providers/openai.js.map +1 -0
- package/dist/embeddings/providers/voyage.d.ts +3 -0
- package/dist/embeddings/providers/voyage.d.ts.map +1 -0
- package/dist/embeddings/providers/voyage.js +12 -0
- package/dist/embeddings/providers/voyage.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/ingest/jobs.d.ts +29 -0
- package/dist/ingest/jobs.d.ts.map +1 -0
- package/dist/ingest/jobs.js +131 -0
- package/dist/ingest/jobs.js.map +1 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +3 -0
- package/dist/logger.js.map +1 -0
- package/dist/mcp-server/server.d.ts +2 -0
- package/dist/mcp-server/server.d.ts.map +1 -0
- package/dist/mcp-server/server.js +3 -0
- package/dist/mcp-server/server.js.map +1 -0
- package/dist/mcp-server/tests/mcp-e2e.test.d.ts +2 -0
- package/dist/mcp-server/tests/mcp-e2e.test.d.ts.map +1 -0
- package/dist/mcp-server/tests/mcp-e2e.test.js +157 -0
- package/dist/mcp-server/tests/mcp-e2e.test.js.map +1 -0
- package/dist/mcp-server/tool-router.d.ts +2 -0
- package/dist/mcp-server/tool-router.d.ts.map +1 -0
- package/dist/mcp-server/tool-router.js +3 -0
- package/dist/mcp-server/tool-router.js.map +1 -0
- package/dist/memory/admin/tools.d.ts +6 -0
- package/dist/memory/admin/tools.d.ts.map +1 -0
- package/dist/memory/admin/tools.js +134 -0
- package/dist/memory/admin/tools.js.map +1 -0
- package/dist/memory/core/chunker.d.ts +6 -0
- package/dist/memory/core/chunker.d.ts.map +1 -0
- package/dist/memory/core/chunker.js +49 -0
- package/dist/memory/core/chunker.js.map +1 -0
- package/dist/memory/core/module-interface.d.ts +23 -0
- package/dist/memory/core/module-interface.d.ts.map +1 -0
- package/dist/memory/core/module-interface.js +2 -0
- package/dist/memory/core/module-interface.js.map +1 -0
- package/dist/memory/core/module-registry.d.ts +14 -0
- package/dist/memory/core/module-registry.d.ts.map +1 -0
- package/dist/memory/core/module-registry.js +45 -0
- package/dist/memory/core/module-registry.js.map +1 -0
- package/dist/memory/core/property-extractor.d.ts +6 -0
- package/dist/memory/core/property-extractor.d.ts.map +1 -0
- package/dist/memory/core/property-extractor.js +90 -0
- package/dist/memory/core/property-extractor.js.map +1 -0
- package/dist/memory/core/reindex.d.ts +11 -0
- package/dist/memory/core/reindex.d.ts.map +1 -0
- package/dist/memory/core/reindex.js +55 -0
- package/dist/memory/core/reindex.js.map +1 -0
- package/dist/memory/core/source-registry.d.ts +42 -0
- package/dist/memory/core/source-registry.d.ts.map +1 -0
- package/dist/memory/core/source-registry.js +86 -0
- package/dist/memory/core/source-registry.js.map +1 -0
- package/dist/memory/core/store.d.ts +40 -0
- package/dist/memory/core/store.d.ts.map +1 -0
- package/dist/memory/core/store.js +257 -0
- package/dist/memory/core/store.js.map +1 -0
- package/dist/memory/core/wikilinks.d.ts +13 -0
- package/dist/memory/core/wikilinks.d.ts.map +1 -0
- package/dist/memory/core/wikilinks.js +25 -0
- package/dist/memory/core/wikilinks.js.map +1 -0
- package/dist/memory/modules/_custom/generic-module.d.ts +7 -0
- package/dist/memory/modules/_custom/generic-module.d.ts.map +1 -0
- package/dist/memory/modules/_custom/generic-module.js +108 -0
- package/dist/memory/modules/_custom/generic-module.js.map +1 -0
- package/dist/memory/modules/_custom/persistence.d.ts +15 -0
- package/dist/memory/modules/_custom/persistence.d.ts.map +1 -0
- package/dist/memory/modules/_custom/persistence.js +47 -0
- package/dist/memory/modules/_custom/persistence.js.map +1 -0
- package/dist/memory/modules/_custom/tests/custom-types.test.d.ts +2 -0
- package/dist/memory/modules/_custom/tests/custom-types.test.d.ts.map +1 -0
- package/dist/memory/modules/_custom/tests/custom-types.test.js +89 -0
- package/dist/memory/modules/_custom/tests/custom-types.test.js.map +1 -0
- package/dist/memory/modules/_custom/tools.d.ts +7 -0
- package/dist/memory/modules/_custom/tools.d.ts.map +1 -0
- package/dist/memory/modules/_custom/tools.js +72 -0
- package/dist/memory/modules/_custom/tools.js.map +1 -0
- package/dist/memory/modules/audio/ingest.d.ts +9 -0
- package/dist/memory/modules/audio/ingest.d.ts.map +1 -0
- package/dist/memory/modules/audio/ingest.js +32 -0
- package/dist/memory/modules/audio/ingest.js.map +1 -0
- package/dist/memory/modules/audio/module.d.ts +6 -0
- package/dist/memory/modules/audio/module.d.ts.map +1 -0
- package/dist/memory/modules/audio/module.js +18 -0
- package/dist/memory/modules/audio/module.js.map +1 -0
- package/dist/memory/modules/audio/tests/audio.test.d.ts +2 -0
- package/dist/memory/modules/audio/tests/audio.test.d.ts.map +1 -0
- package/dist/memory/modules/audio/tests/audio.test.js +57 -0
- package/dist/memory/modules/audio/tests/audio.test.js.map +1 -0
- package/dist/memory/modules/audio/tests/transcriber.test.d.ts +2 -0
- package/dist/memory/modules/audio/tests/transcriber.test.d.ts.map +1 -0
- package/dist/memory/modules/audio/tests/transcriber.test.js +27 -0
- package/dist/memory/modules/audio/tests/transcriber.test.js.map +1 -0
- package/dist/memory/modules/audio/tools.d.ts +5 -0
- package/dist/memory/modules/audio/tools.d.ts.map +1 -0
- package/dist/memory/modules/audio/tools.js +60 -0
- package/dist/memory/modules/audio/tools.js.map +1 -0
- package/dist/memory/modules/audio/transcriber.d.ts +15 -0
- package/dist/memory/modules/audio/transcriber.d.ts.map +1 -0
- package/dist/memory/modules/audio/transcriber.js +177 -0
- package/dist/memory/modules/audio/transcriber.js.map +1 -0
- package/dist/memory/modules/conversations/ingest.d.ts +10 -0
- package/dist/memory/modules/conversations/ingest.d.ts.map +1 -0
- package/dist/memory/modules/conversations/ingest.js +38 -0
- package/dist/memory/modules/conversations/ingest.js.map +1 -0
- package/dist/memory/modules/conversations/module.d.ts +6 -0
- package/dist/memory/modules/conversations/module.d.ts.map +1 -0
- package/dist/memory/modules/conversations/module.js +43 -0
- package/dist/memory/modules/conversations/module.js.map +1 -0
- package/dist/memory/modules/conversations/tests/conversations.test.d.ts +2 -0
- package/dist/memory/modules/conversations/tests/conversations.test.d.ts.map +1 -0
- package/dist/memory/modules/conversations/tests/conversations.test.js +70 -0
- package/dist/memory/modules/conversations/tests/conversations.test.js.map +1 -0
- package/dist/memory/modules/conversations/tools.d.ts +5 -0
- package/dist/memory/modules/conversations/tools.d.ts.map +1 -0
- package/dist/memory/modules/conversations/tools.js +75 -0
- package/dist/memory/modules/conversations/tools.js.map +1 -0
- package/dist/memory/modules/drive/connector.d.ts +19 -0
- package/dist/memory/modules/drive/connector.d.ts.map +1 -0
- package/dist/memory/modules/drive/connector.js +52 -0
- package/dist/memory/modules/drive/connector.js.map +1 -0
- package/dist/memory/modules/drive/ingest.d.ts +9 -0
- package/dist/memory/modules/drive/ingest.d.ts.map +1 -0
- package/dist/memory/modules/drive/ingest.js +27 -0
- package/dist/memory/modules/drive/ingest.js.map +1 -0
- package/dist/memory/modules/drive/module.d.ts +6 -0
- package/dist/memory/modules/drive/module.d.ts.map +1 -0
- package/dist/memory/modules/drive/module.js +31 -0
- package/dist/memory/modules/drive/module.js.map +1 -0
- package/dist/memory/modules/drive/oauth.d.ts +14 -0
- package/dist/memory/modules/drive/oauth.d.ts.map +1 -0
- package/dist/memory/modules/drive/oauth.js +130 -0
- package/dist/memory/modules/drive/oauth.js.map +1 -0
- package/dist/memory/modules/drive/tests/drive.test.d.ts +2 -0
- package/dist/memory/modules/drive/tests/drive.test.d.ts.map +1 -0
- package/dist/memory/modules/drive/tests/drive.test.js +66 -0
- package/dist/memory/modules/drive/tests/drive.test.js.map +1 -0
- package/dist/memory/modules/drive/tools.d.ts +5 -0
- package/dist/memory/modules/drive/tools.d.ts.map +1 -0
- package/dist/memory/modules/drive/tools.js +131 -0
- package/dist/memory/modules/drive/tools.js.map +1 -0
- package/dist/memory/modules/drive/watcher.d.ts +5 -0
- package/dist/memory/modules/drive/watcher.d.ts.map +1 -0
- package/dist/memory/modules/drive/watcher.js +46 -0
- package/dist/memory/modules/drive/watcher.js.map +1 -0
- package/dist/memory/modules/notes/ingest.d.ts +3 -0
- package/dist/memory/modules/notes/ingest.d.ts.map +1 -0
- package/dist/memory/modules/notes/ingest.js +30 -0
- package/dist/memory/modules/notes/ingest.js.map +1 -0
- package/dist/memory/modules/notes/module.d.ts +5 -0
- package/dist/memory/modules/notes/module.d.ts.map +1 -0
- package/dist/memory/modules/notes/module.js +28 -0
- package/dist/memory/modules/notes/module.js.map +1 -0
- package/dist/memory/modules/notes/tests/notes.test.d.ts +2 -0
- package/dist/memory/modules/notes/tests/notes.test.d.ts.map +1 -0
- package/dist/memory/modules/notes/tests/notes.test.js +59 -0
- package/dist/memory/modules/notes/tests/notes.test.js.map +1 -0
- package/dist/memory/modules/notes/tools.d.ts +5 -0
- package/dist/memory/modules/notes/tools.d.ts.map +1 -0
- package/dist/memory/modules/notes/tools.js +69 -0
- package/dist/memory/modules/notes/tools.js.map +1 -0
- package/dist/memory/modules/notion/connector.d.ts +10 -0
- package/dist/memory/modules/notion/connector.d.ts.map +1 -0
- package/dist/memory/modules/notion/connector.js +112 -0
- package/dist/memory/modules/notion/connector.js.map +1 -0
- package/dist/memory/modules/notion/ingest.d.ts +9 -0
- package/dist/memory/modules/notion/ingest.d.ts.map +1 -0
- package/dist/memory/modules/notion/ingest.js +24 -0
- package/dist/memory/modules/notion/ingest.js.map +1 -0
- package/dist/memory/modules/notion/module.d.ts +6 -0
- package/dist/memory/modules/notion/module.d.ts.map +1 -0
- package/dist/memory/modules/notion/module.js +31 -0
- package/dist/memory/modules/notion/module.js.map +1 -0
- package/dist/memory/modules/notion/oauth.d.ts +19 -0
- package/dist/memory/modules/notion/oauth.d.ts.map +1 -0
- package/dist/memory/modules/notion/oauth.js +117 -0
- package/dist/memory/modules/notion/oauth.js.map +1 -0
- package/dist/memory/modules/notion/tests/notion.test.d.ts +2 -0
- package/dist/memory/modules/notion/tests/notion.test.d.ts.map +1 -0
- package/dist/memory/modules/notion/tests/notion.test.js +53 -0
- package/dist/memory/modules/notion/tests/notion.test.js.map +1 -0
- package/dist/memory/modules/notion/tools.d.ts +5 -0
- package/dist/memory/modules/notion/tools.d.ts.map +1 -0
- package/dist/memory/modules/notion/tools.js +116 -0
- package/dist/memory/modules/notion/tools.js.map +1 -0
- package/dist/memory/modules/notion/watcher.d.ts +5 -0
- package/dist/memory/modules/notion/watcher.d.ts.map +1 -0
- package/dist/memory/modules/notion/watcher.js +41 -0
- package/dist/memory/modules/notion/watcher.js.map +1 -0
- package/dist/memory/modules/obsidian/ingest.d.ts +9 -0
- package/dist/memory/modules/obsidian/ingest.d.ts.map +1 -0
- package/dist/memory/modules/obsidian/ingest.js +80 -0
- package/dist/memory/modules/obsidian/ingest.js.map +1 -0
- package/dist/memory/modules/obsidian/module.d.ts +6 -0
- package/dist/memory/modules/obsidian/module.d.ts.map +1 -0
- package/dist/memory/modules/obsidian/module.js +31 -0
- package/dist/memory/modules/obsidian/module.js.map +1 -0
- package/dist/memory/modules/obsidian/tests/obsidian.test.d.ts +2 -0
- package/dist/memory/modules/obsidian/tests/obsidian.test.d.ts.map +1 -0
- package/dist/memory/modules/obsidian/tests/obsidian.test.js +65 -0
- package/dist/memory/modules/obsidian/tests/obsidian.test.js.map +1 -0
- package/dist/memory/modules/obsidian/tests/vault-reader.test.d.ts +2 -0
- package/dist/memory/modules/obsidian/tests/vault-reader.test.d.ts.map +1 -0
- package/dist/memory/modules/obsidian/tests/vault-reader.test.js +37 -0
- package/dist/memory/modules/obsidian/tests/vault-reader.test.js.map +1 -0
- package/dist/memory/modules/obsidian/tools.d.ts +5 -0
- package/dist/memory/modules/obsidian/tools.d.ts.map +1 -0
- package/dist/memory/modules/obsidian/tools.js +101 -0
- package/dist/memory/modules/obsidian/tools.js.map +1 -0
- package/dist/memory/modules/obsidian/vault-reader.d.ts +8 -0
- package/dist/memory/modules/obsidian/vault-reader.d.ts.map +1 -0
- package/dist/memory/modules/obsidian/vault-reader.js +82 -0
- package/dist/memory/modules/obsidian/vault-reader.js.map +1 -0
- package/dist/memory/modules/obsidian/watcher.d.ts +5 -0
- package/dist/memory/modules/obsidian/watcher.d.ts.map +1 -0
- package/dist/memory/modules/obsidian/watcher.js +83 -0
- package/dist/memory/modules/obsidian/watcher.js.map +1 -0
- package/dist/memory/modules/youtube/ingest.d.ts +20 -0
- package/dist/memory/modules/youtube/ingest.d.ts.map +1 -0
- package/dist/memory/modules/youtube/ingest.js +49 -0
- package/dist/memory/modules/youtube/ingest.js.map +1 -0
- package/dist/memory/modules/youtube/module.d.ts +11 -0
- package/dist/memory/modules/youtube/module.d.ts.map +1 -0
- package/dist/memory/modules/youtube/module.js +26 -0
- package/dist/memory/modules/youtube/module.js.map +1 -0
- package/dist/memory/modules/youtube/tests/channel.test.d.ts +2 -0
- package/dist/memory/modules/youtube/tests/channel.test.d.ts.map +1 -0
- package/dist/memory/modules/youtube/tests/channel.test.js +61 -0
- package/dist/memory/modules/youtube/tests/channel.test.js.map +1 -0
- package/dist/memory/modules/youtube/tests/transcript-fetcher.test.d.ts +2 -0
- package/dist/memory/modules/youtube/tests/transcript-fetcher.test.d.ts.map +1 -0
- package/dist/memory/modules/youtube/tests/transcript-fetcher.test.js +23 -0
- package/dist/memory/modules/youtube/tests/transcript-fetcher.test.js.map +1 -0
- package/dist/memory/modules/youtube/tests/youtube.test.d.ts +2 -0
- package/dist/memory/modules/youtube/tests/youtube.test.d.ts.map +1 -0
- package/dist/memory/modules/youtube/tests/youtube.test.js +52 -0
- package/dist/memory/modules/youtube/tests/youtube.test.js.map +1 -0
- package/dist/memory/modules/youtube/tools.d.ts +5 -0
- package/dist/memory/modules/youtube/tools.d.ts.map +1 -0
- package/dist/memory/modules/youtube/tools.js +182 -0
- package/dist/memory/modules/youtube/tools.js.map +1 -0
- package/dist/memory/modules/youtube/transcript-fetcher.d.ts +17 -0
- package/dist/memory/modules/youtube/transcript-fetcher.d.ts.map +1 -0
- package/dist/memory/modules/youtube/transcript-fetcher.js +178 -0
- package/dist/memory/modules/youtube/transcript-fetcher.js.map +1 -0
- package/dist/memory/modules/youtube/watcher.d.ts +30 -0
- package/dist/memory/modules/youtube/watcher.d.ts.map +1 -0
- package/dist/memory/modules/youtube/watcher.js +198 -0
- package/dist/memory/modules/youtube/watcher.js.map +1 -0
- package/dist/memory/public/tools.d.ts +5 -0
- package/dist/memory/public/tools.d.ts.map +1 -0
- package/dist/memory/public/tools.js +1761 -0
- package/dist/memory/public/tools.js.map +1 -0
- package/dist/private/algorithms/chunker-semantic.d.ts +3 -0
- package/dist/private/algorithms/chunker-semantic.d.ts.map +1 -0
- package/dist/private/algorithms/chunker-semantic.js +70 -0
- package/dist/private/algorithms/chunker-semantic.js.map +1 -0
- package/dist/private/algorithms/find-related-smart.d.ts +4 -0
- package/dist/private/algorithms/find-related-smart.d.ts.map +1 -0
- package/dist/private/algorithms/find-related-smart.js +52 -0
- package/dist/private/algorithms/find-related-smart.js.map +1 -0
- package/dist/private/algorithms/graph-semantic-edges.d.ts +4 -0
- package/dist/private/algorithms/graph-semantic-edges.d.ts.map +1 -0
- package/dist/private/algorithms/graph-semantic-edges.js +38 -0
- package/dist/private/algorithms/graph-semantic-edges.js.map +1 -0
- package/dist/private/algorithms/search-all-smart.d.ts +9 -0
- package/dist/private/algorithms/search-all-smart.d.ts.map +1 -0
- package/dist/private/algorithms/search-all-smart.js +62 -0
- package/dist/private/algorithms/search-all-smart.js.map +1 -0
- package/dist/private/index.d.ts +7 -0
- package/dist/private/index.d.ts.map +1 -0
- package/dist/private/index.js +39 -0
- package/dist/private/index.js.map +1 -0
- package/dist/private/prompts/extraction-system.d.ts +2 -0
- package/dist/private/prompts/extraction-system.d.ts.map +1 -0
- package/dist/private/prompts/extraction-system.js +15 -0
- package/dist/private/prompts/extraction-system.js.map +1 -0
- package/dist/private/prompts/suggest-properties.d.ts +2 -0
- package/dist/private/prompts/suggest-properties.d.ts.map +1 -0
- package/dist/private/prompts/suggest-properties.js +18 -0
- package/dist/private/prompts/suggest-properties.js.map +1 -0
- package/dist/private/tests/find-related-smart.test.d.ts +2 -0
- package/dist/private/tests/find-related-smart.test.d.ts.map +1 -0
- package/dist/private/tests/find-related-smart.test.js +86 -0
- package/dist/private/tests/find-related-smart.test.js.map +1 -0
- package/dist/private/tests/property-extractor-smart.test.d.ts +2 -0
- package/dist/private/tests/property-extractor-smart.test.d.ts.map +1 -0
- package/dist/private/tests/property-extractor-smart.test.js +26 -0
- package/dist/private/tests/property-extractor-smart.test.js.map +1 -0
- package/dist/scripts/install-ollama.d.ts +3 -0
- package/dist/scripts/install-ollama.d.ts.map +1 -0
- package/dist/scripts/install-ollama.js +78 -0
- package/dist/scripts/install-ollama.js.map +1 -0
- package/dist/scripts/install.d.ts +3 -0
- package/dist/scripts/install.d.ts.map +1 -0
- package/dist/scripts/install.js +191 -0
- package/dist/scripts/install.js.map +1 -0
- package/dist/scripts/pair.d.ts +3 -0
- package/dist/scripts/pair.d.ts.map +1 -0
- package/dist/scripts/pair.js +78 -0
- package/dist/scripts/pair.js.map +1 -0
- package/dist/scripts/rebuild.d.ts +20 -0
- package/dist/scripts/rebuild.d.ts.map +1 -0
- package/dist/scripts/rebuild.js +171 -0
- package/dist/scripts/rebuild.js.map +1 -0
- package/dist/scripts/reindex.d.ts +3 -0
- package/dist/scripts/reindex.d.ts.map +1 -0
- package/dist/scripts/reindex.js +23 -0
- package/dist/scripts/reindex.js.map +1 -0
- package/dist/scripts/serve.d.ts +3 -0
- package/dist/scripts/serve.d.ts.map +1 -0
- package/dist/scripts/serve.js +57 -0
- package/dist/scripts/serve.js.map +1 -0
- package/dist/scripts/service.d.ts +19 -0
- package/dist/scripts/service.d.ts.map +1 -0
- package/dist/scripts/service.js +257 -0
- package/dist/scripts/service.js.map +1 -0
- package/dist/server/api/daily.d.ts +3 -0
- package/dist/server/api/daily.d.ts.map +1 -0
- package/dist/server/api/daily.js +44 -0
- package/dist/server/api/daily.js.map +1 -0
- package/dist/server/api/graph.d.ts +26 -0
- package/dist/server/api/graph.d.ts.map +1 -0
- package/dist/server/api/graph.js +80 -0
- package/dist/server/api/graph.js.map +1 -0
- package/dist/server/api/integrations.d.ts +4 -0
- package/dist/server/api/integrations.d.ts.map +1 -0
- package/dist/server/api/integrations.js +228 -0
- package/dist/server/api/integrations.js.map +1 -0
- package/dist/server/api/memories.d.ts +4 -0
- package/dist/server/api/memories.d.ts.map +1 -0
- package/dist/server/api/memories.js +267 -0
- package/dist/server/api/memories.js.map +1 -0
- package/dist/server/api/reindex.d.ts +3 -0
- package/dist/server/api/reindex.d.ts.map +1 -0
- package/dist/server/api/reindex.js +18 -0
- package/dist/server/api/reindex.js.map +1 -0
- package/dist/server/api/settings.d.ts +3 -0
- package/dist/server/api/settings.d.ts.map +1 -0
- package/dist/server/api/settings.js +24 -0
- package/dist/server/api/settings.js.map +1 -0
- package/dist/server/api/sources.d.ts +4 -0
- package/dist/server/api/sources.d.ts.map +1 -0
- package/dist/server/api/sources.js +45 -0
- package/dist/server/api/sources.js.map +1 -0
- package/dist/server/api/sync-status.d.ts +3 -0
- package/dist/server/api/sync-status.d.ts.map +1 -0
- package/dist/server/api/sync-status.js +43 -0
- package/dist/server/api/sync-status.js.map +1 -0
- package/dist/server/api/types.d.ts +3 -0
- package/dist/server/api/types.d.ts.map +1 -0
- package/dist/server/api/types.js +20 -0
- package/dist/server/api/types.js.map +1 -0
- package/dist/server/api/views.d.ts +25 -0
- package/dist/server/api/views.d.ts.map +1 -0
- package/dist/server/api/views.js +54 -0
- package/dist/server/api/views.js.map +1 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +3 -0
- package/dist/server/index.js.map +1 -0
- package/dist/sync/apply.d.ts +55 -0
- package/dist/sync/apply.d.ts.map +1 -0
- package/dist/sync/apply.js +277 -0
- package/dist/sync/apply.js.map +1 -0
- package/dist/sync/channel-client.d.ts +27 -0
- package/dist/sync/channel-client.d.ts.map +1 -0
- package/dist/sync/channel-client.js +154 -0
- package/dist/sync/channel-client.js.map +1 -0
- package/dist/sync/cloud-saves.d.ts +49 -0
- package/dist/sync/cloud-saves.d.ts.map +1 -0
- package/dist/sync/cloud-saves.js +182 -0
- package/dist/sync/cloud-saves.js.map +1 -0
- package/dist/sync/ed25519.d.ts +54 -0
- package/dist/sync/ed25519.d.ts.map +1 -0
- package/dist/sync/ed25519.js +136 -0
- package/dist/sync/ed25519.js.map +1 -0
- package/dist/sync/ops-log.d.ts +43 -0
- package/dist/sync/ops-log.d.ts.map +1 -0
- package/dist/sync/ops-log.js +153 -0
- package/dist/sync/ops-log.js.map +1 -0
- package/dist/sync/recovery-setup.d.ts +26 -0
- package/dist/sync/recovery-setup.d.ts.map +1 -0
- package/dist/sync/recovery-setup.js +113 -0
- package/dist/sync/recovery-setup.js.map +1 -0
- package/dist/sync/replay.d.ts +19 -0
- package/dist/sync/replay.d.ts.map +1 -0
- package/dist/sync/replay.js +59 -0
- package/dist/sync/replay.js.map +1 -0
- package/dist/sync/shamir.d.ts +22 -0
- package/dist/sync/shamir.d.ts.map +1 -0
- package/dist/sync/shamir.js +109 -0
- package/dist/sync/shamir.js.map +1 -0
- package/dist/sync/tests/apply.test.d.ts +4 -0
- package/dist/sync/tests/apply.test.d.ts.map +1 -0
- package/dist/sync/tests/apply.test.js +119 -0
- package/dist/sync/tests/apply.test.js.map +1 -0
- package/dist/sync/tests/ops-log.test.d.ts +2 -0
- package/dist/sync/tests/ops-log.test.d.ts.map +1 -0
- package/dist/sync/tests/ops-log.test.js +105 -0
- package/dist/sync/tests/ops-log.test.js.map +1 -0
- package/dist/sync/tests/two-device-sync.test.d.ts +2 -0
- package/dist/sync/tests/two-device-sync.test.d.ts.map +1 -0
- package/dist/sync/tests/two-device-sync.test.js +250 -0
- package/dist/sync/tests/two-device-sync.test.js.map +1 -0
- package/dist/sync/types.d.ts +87 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/sync/types.js +37 -0
- package/dist/sync/types.js.map +1 -0
- package/dist/tests/chunker.test.d.ts +2 -0
- package/dist/tests/chunker.test.d.ts.map +1 -0
- package/dist/tests/chunker.test.js +24 -0
- package/dist/tests/chunker.test.js.map +1 -0
- package/dist/tests/cloud-auth.test.d.ts +2 -0
- package/dist/tests/cloud-auth.test.d.ts.map +1 -0
- package/dist/tests/cloud-auth.test.js +75 -0
- package/dist/tests/cloud-auth.test.js.map +1 -0
- package/dist/tests/cloud-crypto.test.d.ts +2 -0
- package/dist/tests/cloud-crypto.test.d.ts.map +1 -0
- package/dist/tests/cloud-crypto.test.js +58 -0
- package/dist/tests/cloud-crypto.test.js.map +1 -0
- package/dist/tests/cloud-integration.test.d.ts +2 -0
- package/dist/tests/cloud-integration.test.d.ts.map +1 -0
- package/dist/tests/cloud-integration.test.js +193 -0
- package/dist/tests/cloud-integration.test.js.map +1 -0
- package/dist/tests/cloud-pairing.test.d.ts +2 -0
- package/dist/tests/cloud-pairing.test.d.ts.map +1 -0
- package/dist/tests/cloud-pairing.test.js +86 -0
- package/dist/tests/cloud-pairing.test.js.map +1 -0
- package/dist/tests/cloud-saves-integration.test.d.ts +2 -0
- package/dist/tests/cloud-saves-integration.test.d.ts.map +1 -0
- package/dist/tests/cloud-saves-integration.test.js +92 -0
- package/dist/tests/cloud-saves-integration.test.js.map +1 -0
- package/dist/tests/cloud-transit.test.d.ts +2 -0
- package/dist/tests/cloud-transit.test.d.ts.map +1 -0
- package/dist/tests/cloud-transit.test.js +263 -0
- package/dist/tests/cloud-transit.test.js.map +1 -0
- package/dist/tests/config.test.d.ts +2 -0
- package/dist/tests/config.test.d.ts.map +1 -0
- package/dist/tests/config.test.js +25 -0
- package/dist/tests/config.test.js.map +1 -0
- package/dist/tests/db.test.d.ts +2 -0
- package/dist/tests/db.test.d.ts.map +1 -0
- package/dist/tests/db.test.js +75 -0
- package/dist/tests/db.test.js.map +1 -0
- package/dist/tests/embeddings-providers.test.d.ts +2 -0
- package/dist/tests/embeddings-providers.test.d.ts.map +1 -0
- package/dist/tests/embeddings-providers.test.js +62 -0
- package/dist/tests/embeddings-providers.test.js.map +1 -0
- package/dist/tests/embeddings.test.d.ts +2 -0
- package/dist/tests/embeddings.test.d.ts.map +1 -0
- package/dist/tests/embeddings.test.js +22 -0
- package/dist/tests/embeddings.test.js.map +1 -0
- package/dist/tests/integrations-api.test.d.ts +2 -0
- package/dist/tests/integrations-api.test.d.ts.map +1 -0
- package/dist/tests/integrations-api.test.js +129 -0
- package/dist/tests/integrations-api.test.js.map +1 -0
- package/dist/tests/memory-store.test.d.ts +2 -0
- package/dist/tests/memory-store.test.d.ts.map +1 -0
- package/dist/tests/memory-store.test.js +129 -0
- package/dist/tests/memory-store.test.js.map +1 -0
- package/dist/tests/module-registry.test.d.ts +2 -0
- package/dist/tests/module-registry.test.d.ts.map +1 -0
- package/dist/tests/module-registry.test.js +44 -0
- package/dist/tests/module-registry.test.js.map +1 -0
- package/dist/tests/property-extractor.test.d.ts +2 -0
- package/dist/tests/property-extractor.test.d.ts.map +1 -0
- package/dist/tests/property-extractor.test.js +24 -0
- package/dist/tests/property-extractor.test.js.map +1 -0
- package/dist/tests/public-tools.test.d.ts +2 -0
- package/dist/tests/public-tools.test.d.ts.map +1 -0
- package/dist/tests/public-tools.test.js +270 -0
- package/dist/tests/public-tools.test.js.map +1 -0
- package/dist/tests/reindex.test.d.ts +2 -0
- package/dist/tests/reindex.test.d.ts.map +1 -0
- package/dist/tests/reindex.test.js +58 -0
- package/dist/tests/reindex.test.js.map +1 -0
- package/dist/tests/shamir.test.d.ts +2 -0
- package/dist/tests/shamir.test.d.ts.map +1 -0
- package/dist/tests/shamir.test.js +57 -0
- package/dist/tests/shamir.test.js.map +1 -0
- package/dist/tests/source-registry.test.d.ts +2 -0
- package/dist/tests/source-registry.test.d.ts.map +1 -0
- package/dist/tests/source-registry.test.js +58 -0
- package/dist/tests/source-registry.test.js.map +1 -0
- package/dist/tests/types.test.d.ts +2 -0
- package/dist/tests/types.test.d.ts.map +1 -0
- package/dist/tests/types.test.js +26 -0
- package/dist/tests/types.test.js.map +1 -0
- package/dist/tests/vector.test.d.ts +2 -0
- package/dist/tests/vector.test.d.ts.map +1 -0
- package/dist/tests/vector.test.js +61 -0
- package/dist/tests/vector.test.js.map +1 -0
- package/dist/tests/wikilinks.test.d.ts +2 -0
- package/dist/tests/wikilinks.test.d.ts.map +1 -0
- package/dist/tests/wikilinks.test.js +20 -0
- package/dist/tests/wikilinks.test.js.map +1 -0
- package/dist/tools/index.d.ts +22 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +38 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/types.d.ts +134 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +25 -0
- package/dist/types.js.map +1 -0
- package/dist/vector/store.d.ts +28 -0
- package/dist/vector/store.d.ts.map +1 -0
- package/dist/vector/store.js +132 -0
- package/dist/vector/store.js.map +1 -0
- package/dist/webapp/api/daily.d.ts +3 -0
- package/dist/webapp/api/daily.d.ts.map +1 -0
- package/dist/webapp/api/daily.js +44 -0
- package/dist/webapp/api/daily.js.map +1 -0
- package/dist/webapp/api/graph.d.ts +26 -0
- package/dist/webapp/api/graph.d.ts.map +1 -0
- package/dist/webapp/api/graph.js +80 -0
- package/dist/webapp/api/graph.js.map +1 -0
- package/dist/webapp/api/memories.d.ts +4 -0
- package/dist/webapp/api/memories.d.ts.map +1 -0
- package/dist/webapp/api/memories.js +70 -0
- package/dist/webapp/api/memories.js.map +1 -0
- package/dist/webapp/api/reindex.d.ts +3 -0
- package/dist/webapp/api/reindex.d.ts.map +1 -0
- package/dist/webapp/api/reindex.js +18 -0
- package/dist/webapp/api/reindex.js.map +1 -0
- package/dist/webapp/api/settings.d.ts +3 -0
- package/dist/webapp/api/settings.d.ts.map +1 -0
- package/dist/webapp/api/settings.js +24 -0
- package/dist/webapp/api/settings.js.map +1 -0
- package/dist/webapp/api/sources.d.ts +4 -0
- package/dist/webapp/api/sources.d.ts.map +1 -0
- package/dist/webapp/api/sources.js +45 -0
- package/dist/webapp/api/sources.js.map +1 -0
- package/dist/webapp/api/sync-status.d.ts +3 -0
- package/dist/webapp/api/sync-status.d.ts.map +1 -0
- package/dist/webapp/api/sync-status.js +43 -0
- package/dist/webapp/api/sync-status.js.map +1 -0
- package/dist/webapp/api/types.d.ts +3 -0
- package/dist/webapp/api/types.d.ts.map +1 -0
- package/dist/webapp/api/types.js +20 -0
- package/dist/webapp/api/types.js.map +1 -0
- package/dist/webapp/api/views.d.ts +25 -0
- package/dist/webapp/api/views.d.ts.map +1 -0
- package/dist/webapp/api/views.js +54 -0
- package/dist/webapp/api/views.js.map +1 -0
- package/dist/webapp/mcp-http.d.ts +2 -0
- package/dist/webapp/mcp-http.d.ts.map +1 -0
- package/dist/webapp/mcp-http.js +3 -0
- package/dist/webapp/mcp-http.js.map +1 -0
- package/dist/webapp/server.d.ts +2 -0
- package/dist/webapp/server.d.ts.map +1 -0
- package/dist/webapp/server.js +3 -0
- package/dist/webapp/server.js.map +1 -0
- package/dist/webapp/tests/api.test.d.ts +2 -0
- package/dist/webapp/tests/api.test.d.ts.map +1 -0
- package/dist/webapp/tests/api.test.js +125 -0
- package/dist/webapp/tests/api.test.js.map +1 -0
- package/dist/webapp/tests/mcp-http.test.d.ts +2 -0
- package/dist/webapp/tests/mcp-http.test.d.ts.map +1 -0
- package/dist/webapp/tests/mcp-http.test.js +47 -0
- package/dist/webapp/tests/mcp-http.test.js.map +1 -0
- package/dist/webapp/websocket.d.ts +2 -0
- package/dist/webapp/websocket.d.ts.map +1 -0
- package/dist/webapp/websocket.js +3 -0
- package/dist/webapp/websocket.js.map +1 -0
- package/package.json +128 -0
- package/src/private/README.md +49 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// src/sync/cloud-saves.ts
|
|
2
|
+
/**
|
|
3
|
+
* Cloud Saves — nightly encrypted snapshot of SQLite DB.
|
|
4
|
+
*
|
|
5
|
+
* Flow:
|
|
6
|
+
* 1. Checkpoint SQLite WAL and copy the DB file to a temp location.
|
|
7
|
+
* 2. Get the current Lamport timestamp from ops_log.
|
|
8
|
+
* 3. Encrypt the DB bytes with libsodium secretstream (master key).
|
|
9
|
+
* 4. PUT /saves/snapshot → engram-cloud (sends body).
|
|
10
|
+
* 5. Record snapshot in local snapshot_log table.
|
|
11
|
+
* 6. GC local temp files.
|
|
12
|
+
*
|
|
13
|
+
* Bootstrap (new PC):
|
|
14
|
+
* 1. GET /saves/snapshot → list available snapshots.
|
|
15
|
+
* 2. Download latest snapshot.
|
|
16
|
+
* 3. Decrypt → restore SQLite.
|
|
17
|
+
* 4. Fetch and replay ops_log deltas > snapshot lamport_ts (Plan N protocol).
|
|
18
|
+
*/
|
|
19
|
+
import sodium from 'libsodium-wrappers';
|
|
20
|
+
import { createLogger } from '../logger.js';
|
|
21
|
+
import { getDb } from '../db/index.js';
|
|
22
|
+
import { loadConfig } from '../config/index.js';
|
|
23
|
+
import fs from 'fs';
|
|
24
|
+
import path from 'path';
|
|
25
|
+
import os from 'os';
|
|
26
|
+
const log = createLogger('cloud-saves');
|
|
27
|
+
/** In-memory master key — set by the auth/pairing flow. */
|
|
28
|
+
let _masterKey = null;
|
|
29
|
+
let _jwt = null;
|
|
30
|
+
let _cloudBaseUrl = 'https://api.engram-mcp.com';
|
|
31
|
+
export function initCloudSaves(params) {
|
|
32
|
+
_masterKey = params.masterKey;
|
|
33
|
+
_jwt = params.jwt;
|
|
34
|
+
if (params.cloudBaseUrl)
|
|
35
|
+
_cloudBaseUrl = params.cloudBaseUrl;
|
|
36
|
+
log.info('Cloud Saves module initialized');
|
|
37
|
+
}
|
|
38
|
+
export function clearCloudSaves() {
|
|
39
|
+
if (_masterKey)
|
|
40
|
+
_masterKey.fill(0);
|
|
41
|
+
_masterKey = null;
|
|
42
|
+
_jwt = null;
|
|
43
|
+
}
|
|
44
|
+
/** Schedule the nightly snapshot at 03:00 local time. */
|
|
45
|
+
export function scheduleNightlySnapshot() {
|
|
46
|
+
const now = new Date();
|
|
47
|
+
const next = new Date(now);
|
|
48
|
+
next.setHours(3, 0, 0, 0);
|
|
49
|
+
if (next <= now) {
|
|
50
|
+
next.setDate(next.getDate() + 1);
|
|
51
|
+
}
|
|
52
|
+
const msUntilFirst = next.getTime() - now.getTime();
|
|
53
|
+
const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
|
|
54
|
+
log.info(`Nightly snapshot scheduled in ${Math.round(msUntilFirst / 60000)} minutes (03:00 local)`);
|
|
55
|
+
const timeout = setTimeout(async () => {
|
|
56
|
+
await takeSnapshot().catch((err) => log.error(`Nightly snapshot failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
57
|
+
setInterval(async () => {
|
|
58
|
+
await takeSnapshot().catch((err) => log.error(`Nightly snapshot failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
59
|
+
}, TWENTY_FOUR_HOURS);
|
|
60
|
+
}, msUntilFirst);
|
|
61
|
+
return timeout;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Take a snapshot now. Can be called manually (e.g., from a dashboard button).
|
|
65
|
+
*/
|
|
66
|
+
export async function takeSnapshot() {
|
|
67
|
+
if (!_masterKey || !_jwt) {
|
|
68
|
+
throw new Error('Cloud Saves not initialized — call initCloudSaves() first');
|
|
69
|
+
}
|
|
70
|
+
const config = loadConfig();
|
|
71
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'engram-snap-'));
|
|
72
|
+
try {
|
|
73
|
+
// 1. Checkpoint SQLite WAL and copy the DB file
|
|
74
|
+
const db = getDb();
|
|
75
|
+
db.pragma('wal_checkpoint(FULL)');
|
|
76
|
+
const dbPath = path.join(config.dataDir, 'engram.db');
|
|
77
|
+
const dbCopyPath = path.join(tmpDir, 'engram.db');
|
|
78
|
+
fs.copyFileSync(dbPath, dbCopyPath);
|
|
79
|
+
log.info(`DB copied to ${dbCopyPath}`);
|
|
80
|
+
// 2. Get current Lamport timestamp from ops_log
|
|
81
|
+
const lamportRow = db
|
|
82
|
+
.prepare(`SELECT MAX(lamport_ts) as max_ts FROM ops_log`)
|
|
83
|
+
.get();
|
|
84
|
+
const lamportTs = lamportRow?.max_ts ?? 0;
|
|
85
|
+
// 3. Read DB bytes
|
|
86
|
+
const dbBytes = fs.readFileSync(dbCopyPath);
|
|
87
|
+
// 4. Encrypt with libsodium secretstream
|
|
88
|
+
const encryptedBytes = await encryptBuffer(dbBytes, _masterKey);
|
|
89
|
+
log.info(`Snapshot encrypted: ${encryptedBytes.byteLength} bytes`);
|
|
90
|
+
// 5. Upload
|
|
91
|
+
const res = await fetch(`${_cloudBaseUrl}/saves/snapshot`, {
|
|
92
|
+
method: 'PUT',
|
|
93
|
+
headers: {
|
|
94
|
+
'Content-Type': 'application/octet-stream',
|
|
95
|
+
'Content-Length': String(encryptedBytes.byteLength),
|
|
96
|
+
Authorization: `Bearer ${_jwt}`,
|
|
97
|
+
'X-Lamport-Ts': String(lamportTs),
|
|
98
|
+
},
|
|
99
|
+
body: encryptedBytes,
|
|
100
|
+
});
|
|
101
|
+
if (!res.ok) {
|
|
102
|
+
const body = await res.text();
|
|
103
|
+
throw new Error(`Snapshot upload failed: ${res.status} — ${body}`);
|
|
104
|
+
}
|
|
105
|
+
const result = (await res.json());
|
|
106
|
+
const snapshotId = result.id;
|
|
107
|
+
// 6. Record in local snapshot_log
|
|
108
|
+
db.prepare(`INSERT INTO snapshot_log (id, r2_key, lamport_ts, size_bytes, created_at)
|
|
109
|
+
VALUES (?, ?, ?, ?, ?)`).run(snapshotId, result.r2_key, lamportTs, encryptedBytes.byteLength, Date.now());
|
|
110
|
+
log.info(`Snapshot complete: id=${snapshotId}, size=${encryptedBytes.byteLength} bytes`);
|
|
111
|
+
return { id: snapshotId, sizeBytes: encryptedBytes.byteLength };
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Bootstrap a new PC from the latest cloud snapshot.
|
|
119
|
+
* Decrypts the snapshot and restores the SQLite DB in-place.
|
|
120
|
+
* After this, the caller should replay ops_log deltas > lamportTs from the cloud relay.
|
|
121
|
+
*/
|
|
122
|
+
export async function bootstrapFromSnapshot(params) {
|
|
123
|
+
const base = params.cloudBaseUrl ?? 'https://api.engram-mcp.com';
|
|
124
|
+
// List snapshots
|
|
125
|
+
const listRes = await fetch(`${base}/saves/snapshot`, {
|
|
126
|
+
headers: { Authorization: `Bearer ${params.jwt}` },
|
|
127
|
+
});
|
|
128
|
+
if (!listRes.ok)
|
|
129
|
+
throw new Error(`Snapshot list failed: ${listRes.status}`);
|
|
130
|
+
const { snapshots } = (await listRes.json());
|
|
131
|
+
if (!snapshots || snapshots.length === 0) {
|
|
132
|
+
log.info('No cloud snapshots found — fresh start');
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const latest = snapshots[0]; // already ordered DESC by created_at
|
|
136
|
+
if (!latest.available) {
|
|
137
|
+
log.warn('Latest snapshot marked unavailable in R2');
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
log.info(`Bootstrapping from snapshot ${latest.id} (lamport_ts=${latest.lamport_ts})`);
|
|
141
|
+
// Download
|
|
142
|
+
const dlRes = await fetch(`${base}/saves/snapshot/${latest.id}/download`, {
|
|
143
|
+
headers: { Authorization: `Bearer ${params.jwt}` },
|
|
144
|
+
});
|
|
145
|
+
if (!dlRes.ok)
|
|
146
|
+
throw new Error(`Snapshot download failed: ${dlRes.status}`);
|
|
147
|
+
const encryptedBytes = Buffer.from(await dlRes.arrayBuffer());
|
|
148
|
+
// Decrypt
|
|
149
|
+
const plainBytes = await decryptBuffer(encryptedBytes, params.masterKey);
|
|
150
|
+
// Restore: write to a temp file then atomically move to dataDir/engram.db
|
|
151
|
+
const tmpPath = path.join(os.tmpdir(), `engram-restore-${Date.now()}.db`);
|
|
152
|
+
fs.writeFileSync(tmpPath, plainBytes);
|
|
153
|
+
const dbTarget = path.join(params.dataDir, 'engram.db');
|
|
154
|
+
if (fs.existsSync(dbTarget)) {
|
|
155
|
+
fs.copyFileSync(dbTarget, `${dbTarget}.bak-${Date.now()}`);
|
|
156
|
+
}
|
|
157
|
+
fs.renameSync(tmpPath, dbTarget);
|
|
158
|
+
log.info(`DB restored from snapshot. Replay ops > lamport_ts=${latest.lamport_ts}`);
|
|
159
|
+
return { lamportTs: latest.lamport_ts, sizeBytes: encryptedBytes.byteLength };
|
|
160
|
+
}
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// Encryption helpers (libsodium secretstream)
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
export async function encryptBuffer(plain, key) {
|
|
165
|
+
await sodium.ready;
|
|
166
|
+
const { state, header } = sodium.crypto_secretstream_xchacha20poly1305_init_push(new Uint8Array(key));
|
|
167
|
+
const cipherChunk = sodium.crypto_secretstream_xchacha20poly1305_push(state, new Uint8Array(plain), null, sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL);
|
|
168
|
+
// Format: header || ciphertext
|
|
169
|
+
return Buffer.concat([Buffer.from(header), Buffer.from(cipherChunk)]);
|
|
170
|
+
}
|
|
171
|
+
export async function decryptBuffer(encrypted, key) {
|
|
172
|
+
await sodium.ready;
|
|
173
|
+
const HEADER_LEN = sodium.crypto_secretstream_xchacha20poly1305_HEADERBYTES;
|
|
174
|
+
const header = encrypted.subarray(0, HEADER_LEN);
|
|
175
|
+
const cipher = encrypted.subarray(HEADER_LEN);
|
|
176
|
+
const state = sodium.crypto_secretstream_xchacha20poly1305_init_pull(new Uint8Array(header), new Uint8Array(key));
|
|
177
|
+
const result = sodium.crypto_secretstream_xchacha20poly1305_pull(state, new Uint8Array(cipher), null);
|
|
178
|
+
if (!result)
|
|
179
|
+
throw new Error('Snapshot decryption failed — wrong key or corrupted data');
|
|
180
|
+
return Buffer.from(result.message);
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=cloud-saves.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloud-saves.js","sourceRoot":"","sources":["../../src/sync/cloud-saves.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import type { DeviceIdentity } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Generate a new ed25519 keypair.
|
|
5
|
+
* Returns { pubkeyHex, privkeyHex } where both are raw key bytes as hex strings.
|
|
6
|
+
* Raw = the 32-byte public key and 32-byte private key seed.
|
|
7
|
+
*/
|
|
8
|
+
export declare function generateKeypair(): {
|
|
9
|
+
pubkeyHex: string;
|
|
10
|
+
privkeyHex: string;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Sign `data` with the ed25519 private key (raw 32-byte hex seed).
|
|
14
|
+
* Returns the 64-byte signature as a hex string.
|
|
15
|
+
*
|
|
16
|
+
* We reconstruct the PKCS8 DER key from the raw seed.
|
|
17
|
+
* PKCS8 DER for ed25519 private key is:
|
|
18
|
+
* 30 2e SEQUENCE (46 bytes)
|
|
19
|
+
* 02 01 00 INTEGER 0 (version)
|
|
20
|
+
* 30 05 SEQUENCE (5 bytes)
|
|
21
|
+
* 06 03 2b 65 70 OID 1.3.101.112 (Ed25519)
|
|
22
|
+
* 04 22 OCTET STRING (34 bytes)
|
|
23
|
+
* 04 20 OCTET STRING (32 bytes) — the raw seed
|
|
24
|
+
* <32 bytes seed>
|
|
25
|
+
*/
|
|
26
|
+
export declare function signBytes(data: Buffer, privkeyHex: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Verify an ed25519 signature.
|
|
29
|
+
* @param data - original bytes that were signed
|
|
30
|
+
* @param sigHex - 64-byte signature as hex string
|
|
31
|
+
* @param pubkeyHex - 32-byte public key as hex string
|
|
32
|
+
*/
|
|
33
|
+
export declare function verifySignature(data: Buffer, sigHex: string, pubkeyHex: string): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Build the canonical bytes that are signed for an op.
|
|
36
|
+
* Format: `{op_id}|{device_id}|{lamport_ts}|{op_type}|{memory_id}|{payload_enc}|{nonce}`
|
|
37
|
+
* All fields are UTF-8. payload_enc and nonce are base64 strings here.
|
|
38
|
+
*/
|
|
39
|
+
export declare function opCanonicalBytes(fields: {
|
|
40
|
+
op_id: string;
|
|
41
|
+
device_id: string;
|
|
42
|
+
lamport_ts: number;
|
|
43
|
+
op_type: string;
|
|
44
|
+
memory_id: string;
|
|
45
|
+
payload_enc: string;
|
|
46
|
+
nonce: string;
|
|
47
|
+
}): Buffer;
|
|
48
|
+
/**
|
|
49
|
+
* Return the existing device identity or generate a new one and persist it.
|
|
50
|
+
* The private key is stored in the local SQLite DB (protected by filesystem perms + WAL).
|
|
51
|
+
* For higher assurance, this can be replaced with OS keychain calls via keytar.
|
|
52
|
+
*/
|
|
53
|
+
export declare function getOrCreateDeviceIdentity(db: Database.Database): DeviceIdentity;
|
|
54
|
+
//# sourceMappingURL=ed25519.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ed25519.d.ts","sourceRoot":"","sources":["../../src/sync/ed25519.ts"],"names":[],"mappings":"AAQA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAGtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAKjD;;;;GAIG;AACH,wBAAgB,eAAe,IAAI;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAc3E;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAUlE;AAyBD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAcxF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,MAAM,CAWT;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,GAAG,cAAc,CA0B/E"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// src/sync/ed25519.ts
|
|
2
|
+
import { createPrivateKey, createPublicKey, generateKeyPairSync, sign, verify, } from 'node:crypto';
|
|
3
|
+
import { monotonicFactory } from 'ulid';
|
|
4
|
+
import { createLogger } from '../logger.js';
|
|
5
|
+
const log = createLogger('sync:ed25519');
|
|
6
|
+
const ulid = monotonicFactory();
|
|
7
|
+
/**
|
|
8
|
+
* Generate a new ed25519 keypair.
|
|
9
|
+
* Returns { pubkeyHex, privkeyHex } where both are raw key bytes as hex strings.
|
|
10
|
+
* Raw = the 32-byte public key and 32-byte private key seed.
|
|
11
|
+
*/
|
|
12
|
+
export function generateKeypair() {
|
|
13
|
+
const { privateKey, publicKey } = generateKeyPairSync('ed25519');
|
|
14
|
+
// Export raw bytes
|
|
15
|
+
const privRaw = privateKey.export({ type: 'pkcs8', format: 'der' });
|
|
16
|
+
const pubRaw = publicKey.export({ type: 'spki', format: 'der' });
|
|
17
|
+
// Last 32 bytes of PKCS8 DER = the private seed
|
|
18
|
+
// Last 32 bytes of SPKI DER = the public key
|
|
19
|
+
const privHex = Buffer.from(privRaw).subarray(-32).toString('hex');
|
|
20
|
+
const pubHex = Buffer.from(pubRaw).subarray(-32).toString('hex');
|
|
21
|
+
log.debug('generated new ed25519 keypair', { pubHex: pubHex.slice(0, 8) + '…' });
|
|
22
|
+
return { pubkeyHex: pubHex, privkeyHex: privHex };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Sign `data` with the ed25519 private key (raw 32-byte hex seed).
|
|
26
|
+
* Returns the 64-byte signature as a hex string.
|
|
27
|
+
*
|
|
28
|
+
* We reconstruct the PKCS8 DER key from the raw seed.
|
|
29
|
+
* PKCS8 DER for ed25519 private key is:
|
|
30
|
+
* 30 2e SEQUENCE (46 bytes)
|
|
31
|
+
* 02 01 00 INTEGER 0 (version)
|
|
32
|
+
* 30 05 SEQUENCE (5 bytes)
|
|
33
|
+
* 06 03 2b 65 70 OID 1.3.101.112 (Ed25519)
|
|
34
|
+
* 04 22 OCTET STRING (34 bytes)
|
|
35
|
+
* 04 20 OCTET STRING (32 bytes) — the raw seed
|
|
36
|
+
* <32 bytes seed>
|
|
37
|
+
*/
|
|
38
|
+
export function signBytes(data, privkeyHex) {
|
|
39
|
+
const seed = Buffer.from(privkeyHex, 'hex');
|
|
40
|
+
if (seed.length !== 32)
|
|
41
|
+
throw new Error('privkeyHex must be 32 raw bytes (64 hex chars)');
|
|
42
|
+
// Build PKCS8 DER for ed25519 from raw seed
|
|
43
|
+
const pkcs8 = buildEd25519Pkcs8(seed);
|
|
44
|
+
const privKey = createPrivateKey({ key: pkcs8, format: 'der', type: 'pkcs8' });
|
|
45
|
+
const sig = sign(null, data, privKey);
|
|
46
|
+
return Buffer.from(sig).toString('hex');
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Build a PKCS8 DER buffer for an ed25519 private key from a 32-byte seed.
|
|
50
|
+
* This is a fixed-layout encoding — no ASN.1 parser needed.
|
|
51
|
+
*/
|
|
52
|
+
function buildEd25519Pkcs8(seed) {
|
|
53
|
+
// PKCS8 structure for Ed25519:
|
|
54
|
+
// SEQUENCE {
|
|
55
|
+
// INTEGER 0
|
|
56
|
+
// SEQUENCE { OID 1.3.101.112 }
|
|
57
|
+
// OCTET STRING { OCTET STRING { seed } }
|
|
58
|
+
// }
|
|
59
|
+
const oid = Buffer.from([0x06, 0x03, 0x2b, 0x65, 0x70]); // OID Ed25519
|
|
60
|
+
const innerOctet = Buffer.concat([Buffer.from([0x04, 0x20]), seed]); // OCTET STRING(seed)
|
|
61
|
+
const outerOctet = Buffer.concat([
|
|
62
|
+
Buffer.from([0x04, innerOctet.length]),
|
|
63
|
+
innerOctet,
|
|
64
|
+
]);
|
|
65
|
+
const algSeq = Buffer.concat([Buffer.from([0x30, oid.length]), oid]);
|
|
66
|
+
const version = Buffer.from([0x02, 0x01, 0x00]); // INTEGER 0
|
|
67
|
+
const body = Buffer.concat([version, algSeq, outerOctet]);
|
|
68
|
+
return Buffer.concat([Buffer.from([0x30, body.length]), body]);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Verify an ed25519 signature.
|
|
72
|
+
* @param data - original bytes that were signed
|
|
73
|
+
* @param sigHex - 64-byte signature as hex string
|
|
74
|
+
* @param pubkeyHex - 32-byte public key as hex string
|
|
75
|
+
*/
|
|
76
|
+
export function verifySignature(data, sigHex, pubkeyHex) {
|
|
77
|
+
try {
|
|
78
|
+
const pubKey = createPublicKey({
|
|
79
|
+
key: {
|
|
80
|
+
kty: 'OKP',
|
|
81
|
+
crv: 'Ed25519',
|
|
82
|
+
x: Buffer.from(pubkeyHex, 'hex').toString('base64url'),
|
|
83
|
+
},
|
|
84
|
+
format: 'jwk',
|
|
85
|
+
});
|
|
86
|
+
return verify(null, data, pubKey, Buffer.from(sigHex, 'hex'));
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Build the canonical bytes that are signed for an op.
|
|
94
|
+
* Format: `{op_id}|{device_id}|{lamport_ts}|{op_type}|{memory_id}|{payload_enc}|{nonce}`
|
|
95
|
+
* All fields are UTF-8. payload_enc and nonce are base64 strings here.
|
|
96
|
+
*/
|
|
97
|
+
export function opCanonicalBytes(fields) {
|
|
98
|
+
const str = [
|
|
99
|
+
fields.op_id,
|
|
100
|
+
fields.device_id,
|
|
101
|
+
String(fields.lamport_ts),
|
|
102
|
+
fields.op_type,
|
|
103
|
+
fields.memory_id,
|
|
104
|
+
fields.payload_enc,
|
|
105
|
+
fields.nonce,
|
|
106
|
+
].join('|');
|
|
107
|
+
return Buffer.from(str, 'utf8');
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Return the existing device identity or generate a new one and persist it.
|
|
111
|
+
* The private key is stored in the local SQLite DB (protected by filesystem perms + WAL).
|
|
112
|
+
* For higher assurance, this can be replaced with OS keychain calls via keytar.
|
|
113
|
+
*/
|
|
114
|
+
export function getOrCreateDeviceIdentity(db) {
|
|
115
|
+
const existing = db
|
|
116
|
+
.prepare(`SELECT * FROM device_identity LIMIT 1`)
|
|
117
|
+
.get();
|
|
118
|
+
if (existing)
|
|
119
|
+
return existing;
|
|
120
|
+
const { pubkeyHex, privkeyHex } = generateKeypair();
|
|
121
|
+
const deviceId = pubkeyHex; // device_id IS the pubkey — no separate UUID needed
|
|
122
|
+
const createdAt = Date.now();
|
|
123
|
+
// Generate a stable ULID seed — not used here but keeps import alive
|
|
124
|
+
void ulid;
|
|
125
|
+
db.prepare(`INSERT INTO device_identity (device_id, pubkey_hex, privkey_hex, lamport_ts, created_at)
|
|
126
|
+
VALUES (?, ?, ?, 0, ?)`).run(deviceId, pubkeyHex, privkeyHex, createdAt);
|
|
127
|
+
log.info('created new device identity', { deviceId: deviceId.slice(0, 8) + '…' });
|
|
128
|
+
return {
|
|
129
|
+
device_id: deviceId,
|
|
130
|
+
pubkey_hex: pubkeyHex,
|
|
131
|
+
privkey_hex: privkeyHex,
|
|
132
|
+
lamport_ts: 0,
|
|
133
|
+
created_at: createdAt,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=ed25519.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ed25519.js","sourceRoot":"","sources":["../../src/sync/ed25519.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import Database from 'better-sqlite3';
|
|
2
|
+
import type { DeviceIdentity, OpType, WireOp } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Encrypt `plaintext` with a 256-bit master key.
|
|
5
|
+
* Returns { enc: Buffer, nonce: Buffer } where nonce is 12 bytes (GCM standard).
|
|
6
|
+
* The last 16 bytes of `enc` are the GCM auth tag.
|
|
7
|
+
*/
|
|
8
|
+
export declare function encryptPayload(plaintext: Buffer, masterKey: Buffer): {
|
|
9
|
+
enc: Buffer;
|
|
10
|
+
nonce: Buffer;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Decrypt an op payload.
|
|
14
|
+
* The last 16 bytes of `enc` are the GCM auth tag.
|
|
15
|
+
*/
|
|
16
|
+
export declare function decryptPayload(enc: Buffer, nonce: Buffer, masterKey: Buffer): Buffer;
|
|
17
|
+
export declare class OpsLogger {
|
|
18
|
+
#private;
|
|
19
|
+
private db;
|
|
20
|
+
private identity;
|
|
21
|
+
private masterKey;
|
|
22
|
+
constructor(db: Database.Database, identity: DeviceIdentity, masterKey: Buffer);
|
|
23
|
+
/**
|
|
24
|
+
* Append one operation to the ops_log.
|
|
25
|
+
* - Increments the device Lamport clock.
|
|
26
|
+
* - Encrypts the JSON payload with the master key (AES-256-GCM).
|
|
27
|
+
* - Signs canonical bytes with the device private key.
|
|
28
|
+
* - Inserts the row; does NOT set sent_at (pending push).
|
|
29
|
+
* Returns the generated op_id (ULID).
|
|
30
|
+
*/
|
|
31
|
+
append(opType: OpType, memoryId: string, payload: Record<string, unknown>): string;
|
|
32
|
+
/** Mark ops as sent (set sent_at = now). */
|
|
33
|
+
markSent(opIds: string[]): void;
|
|
34
|
+
/** Mark ops as applied locally. */
|
|
35
|
+
markApplied(opIds: string[]): void;
|
|
36
|
+
/** Return ops not yet sent to cloud (sent_at IS NULL). */
|
|
37
|
+
listPending(): WireOp[];
|
|
38
|
+
/** Return the max applied lamport_ts across all devices (for catch-up). */
|
|
39
|
+
maxAppliedLamport(): number;
|
|
40
|
+
/** Return the lexicographically largest op_id that has been applied (any device). */
|
|
41
|
+
maxAppliedOpId(): string | null;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=ops-log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ops-log.d.ts","sourceRoot":"","sources":["../../src/sync/ops-log.ts"],"names":[],"mappings":"AAEA,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAItC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAQjE;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAMhC;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAOpF;AAED,qBAAa,SAAS;;IACpB,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,SAAS,CAAS;gBAEd,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM;IAM9E;;;;;;;OAOG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM;IAiDlF,4CAA4C;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IAS/B,mCAAmC;IACnC,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI;IAQlC,0DAA0D;IAC1D,WAAW,IAAI,MAAM,EAAE;IAmCvB,2EAA2E;IAC3E,iBAAiB,IAAI,MAAM;IAO3B,qFAAqF;IACrF,cAAc,IAAI,MAAM,GAAG,IAAI;CAiBhC"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// src/sync/ops-log.ts
|
|
2
|
+
import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
|
|
3
|
+
import { monotonicFactory } from 'ulid';
|
|
4
|
+
import { createLogger } from '../logger.js';
|
|
5
|
+
import { opCanonicalBytes, signBytes } from './ed25519.js';
|
|
6
|
+
const log = createLogger('sync:ops-log');
|
|
7
|
+
const ulid = monotonicFactory();
|
|
8
|
+
/** AES-256-GCM — NIST-approved, hardware-accelerated on most CPUs. */
|
|
9
|
+
const ALGO = 'aes-256-gcm';
|
|
10
|
+
/**
|
|
11
|
+
* Encrypt `plaintext` with a 256-bit master key.
|
|
12
|
+
* Returns { enc: Buffer, nonce: Buffer } where nonce is 12 bytes (GCM standard).
|
|
13
|
+
* The last 16 bytes of `enc` are the GCM auth tag.
|
|
14
|
+
*/
|
|
15
|
+
export function encryptPayload(plaintext, masterKey) {
|
|
16
|
+
if (masterKey.length !== 32)
|
|
17
|
+
throw new Error('masterKey must be 32 bytes');
|
|
18
|
+
const nonce = randomBytes(12);
|
|
19
|
+
const cipher = createCipheriv(ALGO, masterKey, nonce);
|
|
20
|
+
const enc = Buffer.concat([cipher.update(plaintext), cipher.final(), cipher.getAuthTag()]);
|
|
21
|
+
return { enc, nonce };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Decrypt an op payload.
|
|
25
|
+
* The last 16 bytes of `enc` are the GCM auth tag.
|
|
26
|
+
*/
|
|
27
|
+
export function decryptPayload(enc, nonce, masterKey) {
|
|
28
|
+
if (masterKey.length !== 32)
|
|
29
|
+
throw new Error('masterKey must be 32 bytes');
|
|
30
|
+
const tag = enc.subarray(enc.length - 16);
|
|
31
|
+
const ciphertext = enc.subarray(0, enc.length - 16);
|
|
32
|
+
const decipher = createDecipheriv(ALGO, masterKey, nonce);
|
|
33
|
+
decipher.setAuthTag(tag);
|
|
34
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
35
|
+
}
|
|
36
|
+
export class OpsLogger {
|
|
37
|
+
db;
|
|
38
|
+
identity;
|
|
39
|
+
masterKey;
|
|
40
|
+
constructor(db, identity, masterKey) {
|
|
41
|
+
this.db = db;
|
|
42
|
+
this.identity = identity;
|
|
43
|
+
this.masterKey = masterKey;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Append one operation to the ops_log.
|
|
47
|
+
* - Increments the device Lamport clock.
|
|
48
|
+
* - Encrypts the JSON payload with the master key (AES-256-GCM).
|
|
49
|
+
* - Signs canonical bytes with the device private key.
|
|
50
|
+
* - Inserts the row; does NOT set sent_at (pending push).
|
|
51
|
+
* Returns the generated op_id (ULID).
|
|
52
|
+
*/
|
|
53
|
+
append(opType, memoryId, payload) {
|
|
54
|
+
const opId = ulid();
|
|
55
|
+
const createdAt = Date.now();
|
|
56
|
+
// Lamport increment
|
|
57
|
+
const lamportTs = this.#nextLamport();
|
|
58
|
+
// Encrypt payload
|
|
59
|
+
const plaintext = Buffer.from(JSON.stringify(payload), 'utf8');
|
|
60
|
+
const { enc, nonce } = encryptPayload(plaintext, this.masterKey);
|
|
61
|
+
const payloadEncB64 = enc.toString('base64');
|
|
62
|
+
const nonceB64 = nonce.toString('base64');
|
|
63
|
+
// Sign canonical bytes
|
|
64
|
+
const canonical = opCanonicalBytes({
|
|
65
|
+
op_id: opId,
|
|
66
|
+
device_id: this.identity.device_id,
|
|
67
|
+
lamport_ts: lamportTs,
|
|
68
|
+
op_type: opType,
|
|
69
|
+
memory_id: memoryId,
|
|
70
|
+
payload_enc: payloadEncB64,
|
|
71
|
+
nonce: nonceB64,
|
|
72
|
+
});
|
|
73
|
+
const sigHex = signBytes(canonical, this.identity.privkey_hex);
|
|
74
|
+
// Persist — store raw bytes in BLOB columns
|
|
75
|
+
this.db
|
|
76
|
+
.prepare(`INSERT INTO ops_log
|
|
77
|
+
(op_id, device_id, lamport_ts, op_type, memory_id,
|
|
78
|
+
payload_enc, nonce, sig, applied, created_at)
|
|
79
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`)
|
|
80
|
+
.run(opId, this.identity.device_id, lamportTs, opType, memoryId, enc, // raw encrypted bytes
|
|
81
|
+
nonce, // raw nonce bytes
|
|
82
|
+
Buffer.from(sigHex, 'hex'), // raw sig bytes
|
|
83
|
+
createdAt);
|
|
84
|
+
log.debug('op appended', { opId, opType, memoryId, lamportTs });
|
|
85
|
+
return opId;
|
|
86
|
+
}
|
|
87
|
+
/** Mark ops as sent (set sent_at = now). */
|
|
88
|
+
markSent(opIds) {
|
|
89
|
+
if (opIds.length === 0)
|
|
90
|
+
return;
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
const placeholders = opIds.map(() => '?').join(',');
|
|
93
|
+
this.db
|
|
94
|
+
.prepare(`UPDATE ops_log SET sent_at = ? WHERE op_id IN (${placeholders})`)
|
|
95
|
+
.run(now, ...opIds);
|
|
96
|
+
}
|
|
97
|
+
/** Mark ops as applied locally. */
|
|
98
|
+
markApplied(opIds) {
|
|
99
|
+
if (opIds.length === 0)
|
|
100
|
+
return;
|
|
101
|
+
const placeholders = opIds.map(() => '?').join(',');
|
|
102
|
+
this.db
|
|
103
|
+
.prepare(`UPDATE ops_log SET applied = 1 WHERE op_id IN (${placeholders})`)
|
|
104
|
+
.run(...opIds);
|
|
105
|
+
}
|
|
106
|
+
/** Return ops not yet sent to cloud (sent_at IS NULL). */
|
|
107
|
+
listPending() {
|
|
108
|
+
const rows = this.db
|
|
109
|
+
.prepare(`SELECT op_id, device_id, lamport_ts, op_type, memory_id,
|
|
110
|
+
payload_enc, nonce, sig, created_at
|
|
111
|
+
FROM ops_log
|
|
112
|
+
WHERE sent_at IS NULL
|
|
113
|
+
ORDER BY created_at ASC
|
|
114
|
+
LIMIT 200`)
|
|
115
|
+
.all();
|
|
116
|
+
return rows.map((r) => ({
|
|
117
|
+
op_id: r.op_id,
|
|
118
|
+
device_id: r.device_id,
|
|
119
|
+
lamport_ts: r.lamport_ts,
|
|
120
|
+
op_type: r.op_type,
|
|
121
|
+
memory_id: r.memory_id,
|
|
122
|
+
payload_enc: r.payload_enc.toString('base64'),
|
|
123
|
+
nonce: r.nonce.toString('base64'),
|
|
124
|
+
sig: r.sig.toString('hex'),
|
|
125
|
+
created_at: r.created_at,
|
|
126
|
+
}));
|
|
127
|
+
}
|
|
128
|
+
/** Return the max applied lamport_ts across all devices (for catch-up). */
|
|
129
|
+
maxAppliedLamport() {
|
|
130
|
+
const row = this.db
|
|
131
|
+
.prepare(`SELECT MAX(lamport_ts) as m FROM ops_log WHERE applied = 1`)
|
|
132
|
+
.get();
|
|
133
|
+
return row.m ?? 0;
|
|
134
|
+
}
|
|
135
|
+
/** Return the lexicographically largest op_id that has been applied (any device). */
|
|
136
|
+
maxAppliedOpId() {
|
|
137
|
+
const row = this.db
|
|
138
|
+
.prepare(`SELECT op_id FROM ops_log WHERE applied = 1 ORDER BY op_id DESC LIMIT 1`)
|
|
139
|
+
.get();
|
|
140
|
+
return row?.op_id ?? null;
|
|
141
|
+
}
|
|
142
|
+
#nextLamport() {
|
|
143
|
+
const row = this.db
|
|
144
|
+
.prepare(`SELECT lamport_ts FROM device_identity WHERE device_id = ?`)
|
|
145
|
+
.get(this.identity.device_id);
|
|
146
|
+
const next = (row?.lamport_ts ?? 0) + 1;
|
|
147
|
+
this.db
|
|
148
|
+
.prepare(`UPDATE device_identity SET lamport_ts = ? WHERE device_id = ?`)
|
|
149
|
+
.run(next, this.identity.device_id);
|
|
150
|
+
return next;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=ops-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ops-log.js","sourceRoot":"","sources":["../../src/sync/ops-log.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface RecoverySetupInput {
|
|
2
|
+
/** The user's 32-byte master key (must be held in RAM — never written to disk) */
|
|
3
|
+
masterKey: Buffer;
|
|
4
|
+
/** Exactly 5 trusted email addresses */
|
|
5
|
+
trustedEmails: [string, string, string, string, string];
|
|
6
|
+
/** JWT for cloud API calls */
|
|
7
|
+
jwt: string;
|
|
8
|
+
/** Cloud API base URL, e.g. 'https://api.engram-mcp.com' */
|
|
9
|
+
cloudBaseUrl: string;
|
|
10
|
+
}
|
|
11
|
+
export interface RecoverySetupResult {
|
|
12
|
+
ok: true;
|
|
13
|
+
shardsStored: number;
|
|
14
|
+
/** The KWK, for in-memory ephemeral use only. NEVER log or persist this. */
|
|
15
|
+
kwk: Buffer;
|
|
16
|
+
}
|
|
17
|
+
export declare function setupRecoveryShards(input: RecoverySetupInput): Promise<RecoverySetupResult>;
|
|
18
|
+
/**
|
|
19
|
+
* Decrypt the encrypted master key using the KWK reconstructed from Shamir shares.
|
|
20
|
+
* Called during the recovery restore flow on the client side.
|
|
21
|
+
*
|
|
22
|
+
* @param mkCiphertextHex - hex string: nonce (24 bytes) || ciphertext
|
|
23
|
+
* @param kwk - 32-byte key-wrapping key (reconstructed from 3-of-5 shares)
|
|
24
|
+
*/
|
|
25
|
+
export declare function decryptMasterKey(mkCiphertextHex: string, kwk: Buffer): Promise<Buffer>;
|
|
26
|
+
//# sourceMappingURL=recovery-setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery-setup.d.ts","sourceRoot":"","sources":["../../src/sync/recovery-setup.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,kBAAkB;IACjC,kFAAkF;IAClF,SAAS,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACxD,8BAA8B;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,4DAA4D;IAC5D,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,IAAI,CAAC;IACT,YAAY,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,GAAG,EAAE,MAAM,CAAC;CACb;AA8BD,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,kBAAkB,GACxB,OAAO,CAAC,mBAAmB,CAAC,CAgE9B;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,eAAe,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAmB5F"}
|