mem-os-python 1.6.27__py3-none-any.whl

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 (253) hide show
  1. mem_os_python-1.6.27.dist-info/METADATA +1032 -0
  2. mem_os_python-1.6.27.dist-info/RECORD +253 -0
  3. mem_os_python-1.6.27.dist-info/WHEEL +4 -0
  4. mem_os_python-1.6.27.dist-info/entry_points.txt +2 -0
  5. mem_os_python-1.6.27.dist-info/licenses/LICENSE +21 -0
  6. memos/__init__.py +16 -0
  7. memos/_js/adapters/anthropic.d.ts +279 -0
  8. memos/_js/adapters/anthropic.d.ts.map +1 -0
  9. memos/_js/adapters/anthropic.js +348 -0
  10. memos/_js/adapters/anthropic.js.map +1 -0
  11. memos/_js/adapters/base.d.ts +10 -0
  12. memos/_js/adapters/base.d.ts.map +1 -0
  13. memos/_js/adapters/base.js +2 -0
  14. memos/_js/adapters/base.js.map +1 -0
  15. memos/_js/adapters/openai.d.ts +264 -0
  16. memos/_js/adapters/openai.d.ts.map +1 -0
  17. memos/_js/adapters/openai.js +342 -0
  18. memos/_js/adapters/openai.js.map +1 -0
  19. memos/_js/apps/explorer.d.ts +244 -0
  20. memos/_js/apps/explorer.d.ts.map +1 -0
  21. memos/_js/apps/explorer.js +738 -0
  22. memos/_js/apps/explorer.js.map +1 -0
  23. memos/_js/citations.d.ts +65 -0
  24. memos/_js/citations.d.ts.map +1 -0
  25. memos/_js/citations.js +143 -0
  26. memos/_js/citations.js.map +1 -0
  27. memos/_js/cli-browse.d.ts +3 -0
  28. memos/_js/cli-browse.d.ts.map +1 -0
  29. memos/_js/cli-browse.js +129 -0
  30. memos/_js/cli-browse.js.map +1 -0
  31. memos/_js/cli-completion.d.ts +31 -0
  32. memos/_js/cli-completion.d.ts.map +1 -0
  33. memos/_js/cli-completion.js +340 -0
  34. memos/_js/cli-completion.js.map +1 -0
  35. memos/_js/cli-help.d.ts +18 -0
  36. memos/_js/cli-help.d.ts.map +1 -0
  37. memos/_js/cli-help.js +357 -0
  38. memos/_js/cli-help.js.map +1 -0
  39. memos/_js/cli.d.ts +28 -0
  40. memos/_js/cli.d.ts.map +1 -0
  41. memos/_js/cli.js +2880 -0
  42. memos/_js/cli.js.map +1 -0
  43. memos/_js/compression.d.ts +25 -0
  44. memos/_js/compression.d.ts.map +1 -0
  45. memos/_js/compression.js +319 -0
  46. memos/_js/compression.js.map +1 -0
  47. memos/_js/confidence-machine.d.ts +99 -0
  48. memos/_js/confidence-machine.d.ts.map +1 -0
  49. memos/_js/confidence-machine.js +222 -0
  50. memos/_js/confidence-machine.js.map +1 -0
  51. memos/_js/context-pack.d.ts +506 -0
  52. memos/_js/context-pack.d.ts.map +1 -0
  53. memos/_js/context-pack.js +1435 -0
  54. memos/_js/context-pack.js.map +1 -0
  55. memos/_js/contradictions.d.ts +160 -0
  56. memos/_js/contradictions.d.ts.map +1 -0
  57. memos/_js/contradictions.js +255 -0
  58. memos/_js/contradictions.js.map +1 -0
  59. memos/_js/dashboard.d.ts +30 -0
  60. memos/_js/dashboard.d.ts.map +1 -0
  61. memos/_js/dashboard.js +302 -0
  62. memos/_js/dashboard.js.map +1 -0
  63. memos/_js/ebbinghaus.d.ts +29 -0
  64. memos/_js/ebbinghaus.d.ts.map +1 -0
  65. memos/_js/ebbinghaus.js +32 -0
  66. memos/_js/ebbinghaus.js.map +1 -0
  67. memos/_js/embedding-queue.d.ts +190 -0
  68. memos/_js/embedding-queue.d.ts.map +1 -0
  69. memos/_js/embedding-queue.js +316 -0
  70. memos/_js/embedding-queue.js.map +1 -0
  71. memos/_js/embeddings.d.ts +187 -0
  72. memos/_js/embeddings.d.ts.map +1 -0
  73. memos/_js/embeddings.js +633 -0
  74. memos/_js/embeddings.js.map +1 -0
  75. memos/_js/entity-extraction.d.ts +54 -0
  76. memos/_js/entity-extraction.d.ts.map +1 -0
  77. memos/_js/entity-extraction.js +325 -0
  78. memos/_js/entity-extraction.js.map +1 -0
  79. memos/_js/entity-index.d.ts +93 -0
  80. memos/_js/entity-index.d.ts.map +1 -0
  81. memos/_js/entity-index.js +165 -0
  82. memos/_js/entity-index.js.map +1 -0
  83. memos/_js/event-memory.d.ts +147 -0
  84. memos/_js/event-memory.d.ts.map +1 -0
  85. memos/_js/event-memory.js +290 -0
  86. memos/_js/event-memory.js.map +1 -0
  87. memos/_js/external-import.d.ts +98 -0
  88. memos/_js/external-import.d.ts.map +1 -0
  89. memos/_js/external-import.js +669 -0
  90. memos/_js/external-import.js.map +1 -0
  91. memos/_js/fidelity.d.ts +367 -0
  92. memos/_js/fidelity.d.ts.map +1 -0
  93. memos/_js/fidelity.js +911 -0
  94. memos/_js/fidelity.js.map +1 -0
  95. memos/_js/graph-expansion.d.ts +119 -0
  96. memos/_js/graph-expansion.d.ts.map +1 -0
  97. memos/_js/graph-expansion.js +213 -0
  98. memos/_js/graph-expansion.js.map +1 -0
  99. memos/_js/graph-mermaid.d.ts +13 -0
  100. memos/_js/graph-mermaid.d.ts.map +1 -0
  101. memos/_js/graph-mermaid.js +32 -0
  102. memos/_js/graph-mermaid.js.map +1 -0
  103. memos/_js/graph.d.ts +170 -0
  104. memos/_js/graph.d.ts.map +1 -0
  105. memos/_js/graph.js +482 -0
  106. memos/_js/graph.js.map +1 -0
  107. memos/_js/harness.d.ts +63 -0
  108. memos/_js/harness.d.ts.map +1 -0
  109. memos/_js/harness.js +93 -0
  110. memos/_js/harness.js.map +1 -0
  111. memos/_js/importance.d.ts +118 -0
  112. memos/_js/importance.d.ts.map +1 -0
  113. memos/_js/importance.js +144 -0
  114. memos/_js/importance.js.map +1 -0
  115. memos/_js/index.d.ts +51 -0
  116. memos/_js/index.d.ts.map +1 -0
  117. memos/_js/index.js +53 -0
  118. memos/_js/index.js.map +1 -0
  119. memos/_js/mcp.d.ts +58 -0
  120. memos/_js/mcp.d.ts.map +1 -0
  121. memos/_js/mcp.js +1853 -0
  122. memos/_js/mcp.js.map +1 -0
  123. memos/_js/memory.d.ts +1762 -0
  124. memos/_js/memory.d.ts.map +1 -0
  125. memos/_js/memory.js +6283 -0
  126. memos/_js/memory.js.map +1 -0
  127. memos/_js/procedural.d.ts +74 -0
  128. memos/_js/procedural.d.ts.map +1 -0
  129. memos/_js/procedural.js +125 -0
  130. memos/_js/procedural.js.map +1 -0
  131. memos/_js/project-config.d.ts +68 -0
  132. memos/_js/project-config.d.ts.map +1 -0
  133. memos/_js/project-config.js +59 -0
  134. memos/_js/project-config.js.map +1 -0
  135. memos/_js/proof-of-forgetting.d.ts +57 -0
  136. memos/_js/proof-of-forgetting.d.ts.map +1 -0
  137. memos/_js/proof-of-forgetting.js +80 -0
  138. memos/_js/proof-of-forgetting.js.map +1 -0
  139. memos/_js/provenance.d.ts +122 -0
  140. memos/_js/provenance.d.ts.map +1 -0
  141. memos/_js/provenance.js +182 -0
  142. memos/_js/provenance.js.map +1 -0
  143. memos/_js/quarantine-commands.d.ts +23 -0
  144. memos/_js/quarantine-commands.d.ts.map +1 -0
  145. memos/_js/quarantine-commands.js +42 -0
  146. memos/_js/quarantine-commands.js.map +1 -0
  147. memos/_js/quarantine.d.ts +120 -0
  148. memos/_js/quarantine.d.ts.map +1 -0
  149. memos/_js/quarantine.js +873 -0
  150. memos/_js/quarantine.js.map +1 -0
  151. memos/_js/redact.d.ts +12 -0
  152. memos/_js/redact.d.ts.map +1 -0
  153. memos/_js/redact.js +26 -0
  154. memos/_js/redact.js.map +1 -0
  155. memos/_js/repl.d.ts +57 -0
  156. memos/_js/repl.d.ts.map +1 -0
  157. memos/_js/repl.js +295 -0
  158. memos/_js/repl.js.map +1 -0
  159. memos/_js/rerank-local.d.ts +61 -0
  160. memos/_js/rerank-local.d.ts.map +1 -0
  161. memos/_js/rerank-local.js +210 -0
  162. memos/_js/rerank-local.js.map +1 -0
  163. memos/_js/retain-filter.d.ts +98 -0
  164. memos/_js/retain-filter.d.ts.map +1 -0
  165. memos/_js/retain-filter.js +232 -0
  166. memos/_js/retain-filter.js.map +1 -0
  167. memos/_js/retrieval.d.ts +265 -0
  168. memos/_js/retrieval.d.ts.map +1 -0
  169. memos/_js/retrieval.js +1018 -0
  170. memos/_js/retrieval.js.map +1 -0
  171. memos/_js/revert.d.ts +77 -0
  172. memos/_js/revert.d.ts.map +1 -0
  173. memos/_js/revert.js +252 -0
  174. memos/_js/revert.js.map +1 -0
  175. memos/_js/scope.d.ts +30 -0
  176. memos/_js/scope.d.ts.map +1 -0
  177. memos/_js/scope.js +71 -0
  178. memos/_js/scope.js.map +1 -0
  179. memos/_js/semantic-screen.d.ts +152 -0
  180. memos/_js/semantic-screen.d.ts.map +1 -0
  181. memos/_js/semantic-screen.js +290 -0
  182. memos/_js/semantic-screen.js.map +1 -0
  183. memos/_js/session-hooks.d.ts +127 -0
  184. memos/_js/session-hooks.d.ts.map +1 -0
  185. memos/_js/session-hooks.js +482 -0
  186. memos/_js/session-hooks.js.map +1 -0
  187. memos/_js/storage/sqlite.d.ts +427 -0
  188. memos/_js/storage/sqlite.d.ts.map +1 -0
  189. memos/_js/storage/sqlite.js +2428 -0
  190. memos/_js/storage/sqlite.js.map +1 -0
  191. memos/_js/sync.d.ts +213 -0
  192. memos/_js/sync.d.ts.map +1 -0
  193. memos/_js/sync.js +389 -0
  194. memos/_js/sync.js.map +1 -0
  195. memos/_js/tamper-log.d.ts +98 -0
  196. memos/_js/tamper-log.d.ts.map +1 -0
  197. memos/_js/tamper-log.js +327 -0
  198. memos/_js/tamper-log.js.map +1 -0
  199. memos/_js/temporal.d.ts +92 -0
  200. memos/_js/temporal.d.ts.map +1 -0
  201. memos/_js/temporal.js +536 -0
  202. memos/_js/temporal.js.map +1 -0
  203. memos/_js/time-travel.d.ts +38 -0
  204. memos/_js/time-travel.d.ts.map +1 -0
  205. memos/_js/time-travel.js +46 -0
  206. memos/_js/time-travel.js.map +1 -0
  207. memos/_js/timestamp-format.d.ts +18 -0
  208. memos/_js/timestamp-format.d.ts.map +1 -0
  209. memos/_js/timestamp-format.js +52 -0
  210. memos/_js/timestamp-format.js.map +1 -0
  211. memos/_js/triple-extraction.d.ts +31 -0
  212. memos/_js/triple-extraction.d.ts.map +1 -0
  213. memos/_js/triple-extraction.js +116 -0
  214. memos/_js/triple-extraction.js.map +1 -0
  215. memos/_js/types.d.ts +2474 -0
  216. memos/_js/types.d.ts.map +1 -0
  217. memos/_js/types.js +20 -0
  218. memos/_js/types.js.map +1 -0
  219. memos/_js/updater.d.ts +95 -0
  220. memos/_js/updater.d.ts.map +1 -0
  221. memos/_js/updater.js +268 -0
  222. memos/_js/updater.js.map +1 -0
  223. memos/_js/version.d.ts +17 -0
  224. memos/_js/version.d.ts.map +1 -0
  225. memos/_js/version.js +34 -0
  226. memos/_js/version.js.map +1 -0
  227. memos/_js/vision/clip-query-cache.d.ts +58 -0
  228. memos/_js/vision/clip-query-cache.d.ts.map +1 -0
  229. memos/_js/vision/clip-query-cache.js +79 -0
  230. memos/_js/vision/clip-query-cache.js.map +1 -0
  231. memos/_js/vision/clip.d.ts +113 -0
  232. memos/_js/vision/clip.d.ts.map +1 -0
  233. memos/_js/vision/clip.js +224 -0
  234. memos/_js/vision/clip.js.map +1 -0
  235. memos/_js/vision/fake-clip.d.ts +55 -0
  236. memos/_js/vision/fake-clip.d.ts.map +1 -0
  237. memos/_js/vision/fake-clip.js +122 -0
  238. memos/_js/vision/fake-clip.js.map +1 -0
  239. memos/_js/vision/index.d.ts +15 -0
  240. memos/_js/vision/index.d.ts.map +1 -0
  241. memos/_js/vision/index.js +13 -0
  242. memos/_js/vision/index.js.map +1 -0
  243. memos/_js/vision/store.d.ts +137 -0
  244. memos/_js/vision/store.d.ts.map +1 -0
  245. memos/_js/vision/store.js +383 -0
  246. memos/_js/vision/store.js.map +1 -0
  247. memos/adapters/__init__.py +0 -0
  248. memos/adapters/crewai.py +385 -0
  249. memos/adapters/langchain.py +301 -0
  250. memos/adapters/ollama.py +396 -0
  251. memos/server/__init__.py +0 -0
  252. memos/server/main.py +547 -0
  253. memos/server/routes.py +360 -0
memos/_js/sync.js ADDED
@@ -0,0 +1,389 @@
1
+ /**
2
+ * Encrypted cross-machine sync (bundle format v2).
3
+ *
4
+ * Export memories to an encrypted bundle file, move it to another machine
5
+ * (via cloud drive, USB, etc.), and import it there. The bundle is encrypted
6
+ * with AES-256-GCM; the storage medium never sees plaintext.
7
+ *
8
+ * ## Threat model (v2)
9
+ *
10
+ * - The sync bundle may be stored on untrusted storage (cloud drives, USB
11
+ * sticks, email). EVERYTHING about each record — id, content, type,
12
+ * tags, timestamps, namespace, metadata — is encrypted client-side
13
+ * before writing the bundle. Each record gets a fresh random nonce.
14
+ * - Bundle metadata (`version`, `exportedAt`, `salt`, `recordCount`) is
15
+ * plaintext but AUTHENTICATED: it is bound into every record's
16
+ * AES-GCM additional authenticated data (AAD). Tampering with it
17
+ * fails decryption.
18
+ * - The encryption key is derived from a user passphrase via PBKDF2
19
+ * (100k iterations, SHA-256) or read directly from a key file. The key
20
+ * never leaves the user's devices.
21
+ * - An attacker with the bundle but not the key learns only: the bundle
22
+ * version, export timestamp, salt, record count, and per-record
23
+ * ciphertext sizes. They do NOT learn IDs, content, types, tags,
24
+ * timestamps, or any other memory field.
25
+ * - This does NOT protect against an attacker who obtains the passphrase
26
+ * or key file. Choose a strong passphrase.
27
+ * - This does NOT hide access patterns (when you sync, how many records).
28
+ *
29
+ * ## Conflict semantics
30
+ *
31
+ * - `skip-existing` (default): records with IDs already in the local store
32
+ * are skipped. New records are imported with their original IDs and
33
+ * timestamps.
34
+ * - `last-write-wins`: if a record exists locally, the version with the
35
+ * newer `updatedAt` wins and replaces the local copy (same ID,
36
+ * incoming timestamps preserved). On equal timestamps the local copy
37
+ * is kept (deterministic tiebreak).
38
+ * - Content-identical records are never duplicated: if any local record
39
+ * already has the same content hash, the incoming record is skipped.
40
+ *
41
+ * ## Local-first
42
+ *
43
+ * The local SQLite database remains the source of truth. Sync is additive:
44
+ * export writes a snapshot, import merges into the local store. Import
45
+ * never deletes records except replacing the single conflicting record
46
+ * under `last-write-wins`.
47
+ *
48
+ * ## Transport
49
+ *
50
+ * Transport is manual: you move the bundle file yourself (cloud drive,
51
+ * USB stick, `scp`, …). There is no network sync backend — the bundle
52
+ * file IS the transport, and the encryption above is what makes that safe.
53
+ *
54
+ * @module @memos/sync
55
+ */
56
+ import { createCipheriv, createDecipheriv, createHash, pbkdf2Sync, randomBytes, } from "node:crypto";
57
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
58
+ const SYNC_VERSION = 2;
59
+ const PBKDF2_ITERATIONS = 100_000;
60
+ const KEY_LENGTH = 32; // AES-256
61
+ const NONCE_LENGTH = 12; // GCM standard
62
+ const SALT_LENGTH = 16;
63
+ const MEMORY_TYPES = [
64
+ "fact",
65
+ "preference",
66
+ "context",
67
+ "relationship",
68
+ "entity",
69
+ "custom",
70
+ ];
71
+ /**
72
+ * Derive a 32-byte AES key from a passphrase using PBKDF2.
73
+ */
74
+ export function deriveKey(passphrase, salt) {
75
+ return pbkdf2Sync(passphrase, salt, PBKDF2_ITERATIONS, KEY_LENGTH, "sha256");
76
+ }
77
+ /**
78
+ * Build the AES-GCM additional authenticated data for a bundle.
79
+ * Binds the plaintext bundle metadata to every record's ciphertext so
80
+ * any tampering with version/exportedAt/salt/recordCount fails auth.
81
+ * Key order is fixed so encrypt and decrypt compute identical bytes.
82
+ */
83
+ export function buildAAD(bundle) {
84
+ return Buffer.from(JSON.stringify({
85
+ v: bundle.version,
86
+ exportedAt: bundle.exportedAt,
87
+ salt: bundle.salt,
88
+ recordCount: bundle.recordCount,
89
+ }), "utf-8");
90
+ }
91
+ /**
92
+ * Resolve the raw encryption key from a passphrase or key buffer.
93
+ * Raw-key length errors use a constant message (never derived from key
94
+ * material — see the CodeQL js/clear-text-logging note in v1).
95
+ */
96
+ function resolveKey(keyOrPassphrase, salt) {
97
+ if (typeof keyOrPassphrase === "string") {
98
+ return deriveKey(keyOrPassphrase, salt);
99
+ }
100
+ if (keyOrPassphrase.length !== KEY_LENGTH) {
101
+ throw new Error(`raw key must be ${KEY_LENGTH} bytes`);
102
+ }
103
+ return keyOrPassphrase;
104
+ }
105
+ /**
106
+ * Encrypt a plaintext string with AES-256-GCM.
107
+ * `aad`, when given, is authenticated but not encrypted.
108
+ * Returns { nonce, ciphertext, tag } as base64 strings.
109
+ */
110
+ export function encryptRecord(plaintext, key, aad) {
111
+ const nonce = randomBytes(NONCE_LENGTH);
112
+ const cipher = createCipheriv("aes-256-gcm", key, nonce);
113
+ if (aad)
114
+ cipher.setAAD(aad);
115
+ const ciphertext = Buffer.concat([
116
+ cipher.update(plaintext, "utf-8"),
117
+ cipher.final(),
118
+ ]);
119
+ const tag = cipher.getAuthTag();
120
+ return {
121
+ nonce: nonce.toString("base64"),
122
+ ciphertext: ciphertext.toString("base64"),
123
+ tag: tag.toString("base64"),
124
+ };
125
+ }
126
+ /**
127
+ * Decrypt a record. Throws if the key is wrong, data is tampered,
128
+ * or the AAD does not match.
129
+ */
130
+ export function decryptRecord(encrypted, key, aad) {
131
+ const nonce = Buffer.from(encrypted.nonce, "base64");
132
+ const ciphertext = Buffer.from(encrypted.ciphertext, "base64");
133
+ const tag = Buffer.from(encrypted.tag, "base64");
134
+ const decipher = createDecipheriv("aes-256-gcm", key, nonce);
135
+ if (aad)
136
+ decipher.setAAD(aad);
137
+ decipher.setAuthTag(tag);
138
+ const plaintext = Buffer.concat([
139
+ decipher.update(ciphertext),
140
+ decipher.final(),
141
+ ]);
142
+ return plaintext.toString("utf-8");
143
+ }
144
+ /**
145
+ * Content hash for dedup: SHA-256 over the record's content.
146
+ */
147
+ export function contentHash(content) {
148
+ return createHash("sha256").update(content, "utf-8").digest("hex");
149
+ }
150
+ /** Validate a decrypted record's shape; throws on mismatch. */
151
+ function validateSyncRecord(data) {
152
+ if (!data || typeof data !== "object") {
153
+ throw new Error("bundle contains a malformed record");
154
+ }
155
+ const r = data;
156
+ if (typeof r.id !== "string" || !r.id) {
157
+ throw new Error("bundle record has an invalid id");
158
+ }
159
+ if (typeof r.content !== "string") {
160
+ throw new Error(`bundle record ${r.id} has invalid content`);
161
+ }
162
+ if (typeof r.type !== "string" ||
163
+ !MEMORY_TYPES.includes(r.type)) {
164
+ throw new Error(`bundle record ${r.id} has invalid type`);
165
+ }
166
+ if (typeof r.createdAt !== "number" || typeof r.updatedAt !== "number") {
167
+ throw new Error(`bundle record ${r.id} has invalid timestamps`);
168
+ }
169
+ return {
170
+ id: r.id,
171
+ content: r.content,
172
+ summary: typeof r.summary === "string" ? r.summary : undefined,
173
+ type: r.type,
174
+ tags: Array.isArray(r.tags)
175
+ ? r.tags.filter((t) => typeof t === "string")
176
+ : [],
177
+ importance: typeof r.importance === "number" ? r.importance : 0.5,
178
+ createdAt: r.createdAt,
179
+ updatedAt: r.updatedAt,
180
+ namespace: typeof r.namespace === "string" ? r.namespace : "default",
181
+ metadata: r.metadata && typeof r.metadata === "object"
182
+ ? r.metadata
183
+ : {},
184
+ source: typeof r.source === "string" ? r.source : undefined,
185
+ provenance: typeof r.provenance === "string"
186
+ ? r.provenance
187
+ : undefined,
188
+ pool: r.pool === "note" || r.pool === "procedure" ? r.pool : "event",
189
+ expiresAt: typeof r.expiresAt === "number" ? r.expiresAt : null,
190
+ validFrom: typeof r.validFrom === "number" ? r.validFrom : null,
191
+ validTo: typeof r.validTo === "number" ? r.validTo : null,
192
+ harness: typeof r.harness === "string" ? r.harness : undefined,
193
+ };
194
+ }
195
+ /**
196
+ * Create an encrypted sync bundle (v2) from memory records.
197
+ *
198
+ * Every record field is encrypted; only the bundle envelope
199
+ * (version/exportedAt/salt/recordCount) is plaintext, and it is
200
+ * authenticated via GCM AAD.
201
+ */
202
+ export function createBundle(records, keyOrPassphrase) {
203
+ const salt = randomBytes(SALT_LENGTH);
204
+ const key = resolveKey(keyOrPassphrase, salt);
205
+ const exportedAt = new Date().toISOString();
206
+ const aad = buildAAD({
207
+ version: SYNC_VERSION,
208
+ exportedAt,
209
+ salt: salt.toString("base64"),
210
+ recordCount: records.length,
211
+ });
212
+ const encryptedRecords = records.map((r) => {
213
+ const plaintext = JSON.stringify({
214
+ id: r.id,
215
+ content: r.content,
216
+ summary: r.summary,
217
+ type: r.type,
218
+ tags: r.tags,
219
+ importance: r.importance,
220
+ createdAt: r.createdAt,
221
+ updatedAt: r.updatedAt,
222
+ namespace: r.namespace,
223
+ metadata: r.metadata,
224
+ source: r.source,
225
+ provenance: r.provenance,
226
+ pool: r.pool,
227
+ expiresAt: r.expiresAt ?? null,
228
+ validFrom: r.validFrom ?? null,
229
+ validTo: r.validTo ?? null,
230
+ harness: r.harness,
231
+ });
232
+ const { nonce, ciphertext, tag } = encryptRecord(plaintext, key, aad);
233
+ return { nonce, ciphertext, tag };
234
+ });
235
+ return {
236
+ version: SYNC_VERSION,
237
+ exportedAt,
238
+ salt: salt.toString("base64"),
239
+ recordCount: records.length,
240
+ records: encryptedRecords,
241
+ };
242
+ }
243
+ /**
244
+ * Decrypt all records in a bundle. Returns validated SyncRecords with
245
+ * their original IDs and timestamps.
246
+ *
247
+ * Rejects v1 bundles outright: v1 left record IDs and timestamps in
248
+ * plaintext and cannot be upgraded safely — re-export from the source
249
+ * machine with the current memos.
250
+ */
251
+ export function decryptBundle(bundle, keyOrPassphrase) {
252
+ if (bundle.version === 1) {
253
+ throw new Error("unsupported bundle version 1: v1 bundles expose record IDs and " +
254
+ "timestamps in plaintext and cannot be imported. Re-export from the " +
255
+ "source machine with the current memos version.");
256
+ }
257
+ if (bundle.version !== SYNC_VERSION) {
258
+ throw new Error(`unsupported bundle version: ${bundle.version}`);
259
+ }
260
+ if (!Array.isArray(bundle.records) ||
261
+ bundle.recordCount !== bundle.records.length) {
262
+ throw new Error("bundle metadata mismatch: recordCount does not match records");
263
+ }
264
+ const salt = Buffer.from(bundle.salt, "base64");
265
+ const key = resolveKey(keyOrPassphrase, salt);
266
+ const aad = buildAAD(bundle);
267
+ return bundle.records.map((r) => {
268
+ const plaintext = decryptRecord({ nonce: r.nonce, ciphertext: r.ciphertext, tag: r.tag }, key, aad);
269
+ return validateSyncRecord(JSON.parse(plaintext));
270
+ });
271
+ }
272
+ /**
273
+ * Write a bundle to a file (JSON).
274
+ */
275
+ export function writeBundle(path, bundle) {
276
+ writeFileSync(path, JSON.stringify(bundle, null, 2), "utf-8");
277
+ }
278
+ /**
279
+ * Read a bundle from a file. Validates the envelope shape only —
280
+ * decryption (which authenticates the envelope) happens in
281
+ * `decryptBundle`.
282
+ */
283
+ export function readBundle(path) {
284
+ if (!existsSync(path)) {
285
+ throw new Error(`bundle file not found: ${path}`);
286
+ }
287
+ const data = JSON.parse(readFileSync(path, "utf-8"));
288
+ if (typeof data.version !== "number" ||
289
+ typeof data.exportedAt !== "string" ||
290
+ typeof data.salt !== "string" ||
291
+ typeof data.recordCount !== "number" ||
292
+ !Array.isArray(data.records)) {
293
+ throw new Error("invalid sync bundle format");
294
+ }
295
+ return data;
296
+ }
297
+ /**
298
+ * Merge decrypted records into a MemOS instance, preserving original
299
+ * IDs and timestamps.
300
+ *
301
+ * - Content-identical records (by SHA-256 of content) are never
302
+ * duplicated: skipped when any local record already has the hash.
303
+ * - `skip-existing`: IDs already present are skipped.
304
+ * - `last-write-wins`: when the incoming `updatedAt` is newer than the
305
+ * local one, the local record is replaced (same ID, incoming
306
+ * timestamps preserved). Equal timestamps keep the local copy.
307
+ */
308
+ export async function importRecords(memos, records, strategy) {
309
+ const result = {
310
+ imported: 0,
311
+ skipped: 0,
312
+ updated: 0,
313
+ errors: [],
314
+ };
315
+ const graph = await memos.getGraph();
316
+ const nodes = graph.nodes ?? [];
317
+ const updatedAtById = new Map();
318
+ const hashes = new Set();
319
+ for (const n of nodes) {
320
+ updatedAtById.set(n.id, n.updatedAt ?? 0);
321
+ hashes.add(contentHash(n.content));
322
+ }
323
+ for (const r of records) {
324
+ try {
325
+ // Content-hash dedup first: identical content is never duplicated,
326
+ // regardless of ID or strategy.
327
+ if (hashes.has(contentHash(r.content))) {
328
+ result.skipped++;
329
+ continue;
330
+ }
331
+ const existingUpdatedAt = updatedAtById.get(r.id);
332
+ if (existingUpdatedAt === undefined) {
333
+ await memos.importRecord(r);
334
+ updatedAtById.set(r.id, r.updatedAt);
335
+ hashes.add(contentHash(r.content));
336
+ result.imported++;
337
+ continue;
338
+ }
339
+ if (strategy === "last-write-wins" && r.updatedAt > existingUpdatedAt) {
340
+ await memos.forget(r.id);
341
+ await memos.importRecord(r);
342
+ updatedAtById.set(r.id, r.updatedAt);
343
+ hashes.add(contentHash(r.content));
344
+ result.updated++;
345
+ }
346
+ else {
347
+ result.skipped++;
348
+ }
349
+ }
350
+ catch (err) {
351
+ result.errors.push(`${r.id}: ${err instanceof Error ? err.message : String(err)}`);
352
+ }
353
+ }
354
+ return result;
355
+ }
356
+ /**
357
+ * Resolve the sync key from CLI options.
358
+ * Priority: --key-file > --key > MEMOS_SYNC_KEY env var.
359
+ * When none is provided, throws — the CLI prompts interactively on a TTY.
360
+ */
361
+ /**
362
+ * Resolve the sync key from CLI options.
363
+ * Priority: --passphrase-file > --passphrase > MEMOS_SYNC_KEY env var.
364
+ * When none is provided, throws — the CLI prompts interactively on a TTY.
365
+ *
366
+ * Note: the flag is --passphrase (not --key) because --key is already the
367
+ * global at-rest database encryption key in the CLI.
368
+ */
369
+ export function resolveSyncKey(opts) {
370
+ if (opts.passphraseFile) {
371
+ const keyData = readFileSync(opts.passphraseFile);
372
+ // Allow both raw 32-byte keys and hex-encoded keys
373
+ const hex = keyData.toString("utf-8").trim();
374
+ if (/^[0-9a-fA-F]{64}$/.test(hex)) {
375
+ return Buffer.from(hex, "hex");
376
+ }
377
+ if (keyData.length === KEY_LENGTH) {
378
+ return keyData;
379
+ }
380
+ throw new Error(`key file must contain a 32-byte raw key or 64-char hex string, got ${keyData.length} bytes`);
381
+ }
382
+ if (opts.passphrase)
383
+ return opts.passphrase;
384
+ const envKey = process.env.MEMOS_SYNC_KEY;
385
+ if (envKey)
386
+ return envKey;
387
+ throw new Error("sync key required: use --passphrase <passphrase>, --passphrase-file <path>, or MEMOS_SYNC_KEY env var");
388
+ }
389
+ //# sourceMappingURL=sync.js.map
memos/_js/sync.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Tamper-evident mutation log.
3
+ *
4
+ * Every memory mutation (store / import / update / forget) appends one
5
+ * entry to an append-only, hash-chained log stored alongside the nodes
6
+ * in the same database. `verifyTamperLog()` recomputes the chain and —
7
+ * with the deep check — cross-checks each node's current full state
8
+ * against the latest log entry, so it detects:
9
+ *
10
+ * - edited, deleted, or reordered log rows (chain linkage breaks),
11
+ * - nodes edited/deleted directly in the DB without a log entry
12
+ * (state-hash mismatch / missing-entry mismatch),
13
+ * - nodes resurrected after a `forget` entry.
14
+ *
15
+ * Honest threat model: the log is tamper-*evident*, not tamper-*proof*.
16
+ * An attacker with full write access to the database file can rewrite
17
+ * the entire chain from genesis and the log alone cannot tell. What
18
+ * stops that is an externally anchored checkpoint: `memos log
19
+ * checkpoint` prints the tip hash; write it down (paper, another
20
+ * machine, a signed commit) and `memos log verify --expect <hash>`
21
+ * later proves the chain still ends where you left it. Without an
22
+ * external anchor, a full-chain rewrite is undetectable — by anyone,
23
+ * with any local-only scheme.
24
+ */
25
+ import type { MemoryNode, StorageAdapter, TamperOp } from "./types.js";
26
+ /** Fixed `prevHash` for the first entry — a domain-separated constant. */
27
+ export declare const GENESIS_PREV: string;
28
+ export declare function sha256hex(s: string): string;
29
+ /**
30
+ * Canonical JSON: object keys sorted recursively, arrays in order.
31
+ * Two equal states always serialize to the same string regardless of
32
+ * insertion order — required for a stable hash.
33
+ */
34
+ export declare function canonicalJson(value: unknown): string;
35
+ /**
36
+ * Hash of a node's full mutable state at mutation time. Covers content,
37
+ * summary, type, tags (sorted), importance, namespace, metadata,
38
+ * provenance/source, validity window, TTL, and creation/update
39
+ * timestamps — everything a mutation could change. Deliberately excludes
40
+ * `accessCount`/`lastAccessed`: reads bump those without a log entry,
41
+ * so hashing them would false-positive on every retrieval.
42
+ */
43
+ export declare function nodeHash(node: MemoryNode): string;
44
+ /**
45
+ * Hash of one log entry. The chain position `seq` is bound into the
46
+ * hash, so an entry cannot be replayed at a different position without
47
+ * invalidating it. Canonical field order and a versioned prefix make
48
+ * the serialization unambiguous — no JSON key-ordering games.
49
+ */
50
+ export declare function entryHash(e: {
51
+ seq: number;
52
+ ts: number;
53
+ op: TamperOp;
54
+ nodeId: string;
55
+ nodeHash: string;
56
+ prevHash: string;
57
+ }): string;
58
+ /**
59
+ * Append one mutation to the log. Fail-open: when the storage adapter
60
+ * doesn't implement the log, or the write fails, the mutation itself
61
+ * is never blocked — the log is evidence, not a gate. The adapter
62
+ * assigns the chain position atomically (tip lookup, seq allocation,
63
+ * hash, insert in one transaction).
64
+ */
65
+ export declare function appendTamperEntry(storage: StorageAdapter, op: TamperOp, node: MemoryNode, ts?: number): Promise<void>;
66
+ export interface TamperVerifyIssue {
67
+ kind: "broken_chain" | "content_mismatch" | "deleted_without_log" | "resurrected" | "tip_mismatch";
68
+ seq?: number;
69
+ nodeId?: string;
70
+ detail: string;
71
+ }
72
+ export interface TamperVerifyResult {
73
+ ok: boolean;
74
+ supported: boolean;
75
+ entriesChecked: number;
76
+ /** Nodes that exist but predate the log (no entries) — informational. */
77
+ unloggedNodes: number;
78
+ /**
79
+ * Entries written by the first-day content-only format (v1). Their
80
+ * chain linkage is verified and their content cross-checked, but
81
+ * they predate full-state hashing — informational.
82
+ */
83
+ legacyEntries: number;
84
+ issues: TamperVerifyIssue[];
85
+ tipHash: string | null;
86
+ }
87
+ /**
88
+ * Verify the log: chain integrity (seq continuity, prev-hash linkage,
89
+ * entry-hash recomputation) plus a deep cross-check of every node's
90
+ * current content against its latest log entry.
91
+ *
92
+ * `expectTip` anchors the chain to an externally recorded checkpoint:
93
+ * verification fails unless the current tip hash equals it.
94
+ */
95
+ export declare function verifyTamperLog(storage: StorageAdapter, opts?: {
96
+ expectTip?: string;
97
+ }): Promise<TamperVerifyResult>;
98
+ //# sourceMappingURL=tamper-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tamper-log.d.ts","sourceRoot":"","sources":["../src/tamper-log.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EACV,UAAU,EAEV,cAAc,EAEd,QAAQ,EACT,MAAM,YAAY,CAAC;AAEpB,0EAA0E;AAC1E,eAAO,MAAM,YAAY,QAEiD,CAAC;AAE3E,wBAAgB,SAAS,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAapD;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAiCjD;AA+BD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,QAAQ,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,MAAM,CAYT;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,cAAc,EACvB,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE,UAAU,EAChB,EAAE,GAAE,MAAmB,GACtB,OAAO,CAAC,IAAI,CAAC,CAYf;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EACA,cAAc,GACd,kBAAkB,GAClB,qBAAqB,GACrB,aAAa,GACb,cAAc,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,OAAO,CAAC;IACZ,SAAS,EAAE,OAAO,CAAC;IACnB,cAAc,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,aAAa,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAC5B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,cAAc,EACvB,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAChC,OAAO,CAAC,kBAAkB,CAAC,CA8K7B"}