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/server/main.py ADDED
@@ -0,0 +1,547 @@
1
+ """
2
+ MemOS Python HTTP Server — FastAPI application entry point.
3
+
4
+ Provides a REST API to the MemOS memory layer, allowing Python
5
+ applications (and non-JS frameworks) to interact with MemOS
6
+ over HTTP. The server wraps a headless Node.js subprocess that
7
+ runs the TypeScript SDK.
8
+
9
+ Usage:
10
+ python -m memos.server.main # Default: localhost:7400
11
+ MEMOS_PORT=8080 python -m memos.server.main
12
+ memos-server backup # Backup the database
13
+ memos-server restore <path> # Restore from backup
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import os
20
+ import shutil
21
+ import subprocess
22
+ import sys
23
+ from contextlib import asynccontextmanager
24
+ from pathlib import Path
25
+
26
+ from fastapi import FastAPI
27
+ from fastapi.middleware.cors import CORSMiddleware
28
+
29
+ from .. import __version__
30
+ from .routes import router
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # Configuration
34
+ # ---------------------------------------------------------------------------
35
+
36
+ MEMOS_PORT = int(os.environ.get("MEMOS_PORT", 7400))
37
+ MEMOS_HOST = os.environ.get("MEMOS_HOST", "0.0.0.0")
38
+ MEMOS_DB_PATH = os.environ.get("MEMOS_DB_PATH", str(Path.home() / ".memos" / "memos.db"))
39
+ MEMOS_LOG_LEVEL = os.environ.get("MEMOS_LOG_LEVEL", "info").lower()
40
+
41
+ # better-sqlite3 is a native Node module, so it cannot ship inside the
42
+ # Python wheel — it is installed via npm on first server start when the
43
+ # bundled SDK copy is used (see _ensure_runtime_sdk below).
44
+ _BETTER_SQLITE3_SPEC = "better-sqlite3@^12.11.1"
45
+
46
+
47
+ # ---------------------------------------------------------------------------
48
+ # Node.js SDK resolution
49
+ # ---------------------------------------------------------------------------
50
+
51
+
52
+ def _find_node_binary() -> str:
53
+ """Locate the Node.js binary on the system."""
54
+ node = shutil.which("node")
55
+ if node is None:
56
+ raise RuntimeError(
57
+ "Node.js not found. Install Node.js >= 18 to run the MemOS server.\n"
58
+ " https://nodejs.org/"
59
+ )
60
+ return node
61
+
62
+
63
+ def _native_binding_present(pkg_dir: Path) -> bool:
64
+ """True if better-sqlite3's native binding was actually built/installed.
65
+
66
+ A bare ``node_modules/better-sqlite3`` folder is not enough — if npm's
67
+ install scripts were blocked, the folder exists but the ``.node`` binary
68
+ is missing and ``require()`` crashes at runtime.
69
+ """
70
+ if (pkg_dir / "build" / "Release" / "better_sqlite3.node").is_file():
71
+ return True
72
+ # prebuild-install layout: prebuilds/<platform-arch>/*.node
73
+ prebuilds = pkg_dir / "prebuilds"
74
+ if prebuilds.is_dir():
75
+ for sub in prebuilds.iterdir():
76
+ if sub.is_dir() and any(sub.glob("*.node")):
77
+ return True
78
+ return False
79
+
80
+
81
+ def _better_sqlite3_in(node_modules: Path) -> bool:
82
+ """True if better-sqlite3 sits in THIS node_modules with a native binding.
83
+
84
+ Deliberately does not walk up the directory tree: ancestor node_modules
85
+ (e.g. a stray one in the user's home directory) may hold a broken copy
86
+ that Node would resolve and crash on.
87
+ """
88
+ pkg = node_modules / "better-sqlite3"
89
+ return pkg.is_dir() and _native_binding_present(pkg)
90
+
91
+
92
+ def _ensure_runtime_sdk() -> Path:
93
+ """Return a directory containing index.js plus a working better-sqlite3.
94
+
95
+ Two layouts are supported:
96
+
97
+ 1. **Source checkout** — the compiled ``dist/`` sits below the repo's
98
+ ``node_modules`` (from ``npm install``), so it is used directly.
99
+ 2. **pip-installed wheel** — the compiled SDK is bundled at
100
+ ``memos/_js``. It is staged into ``~/.memos/runtime/js`` and
101
+ better-sqlite3 is npm-installed there on first run (the native
102
+ module cannot ship inside a wheel).
103
+ """
104
+ here = Path(__file__).resolve().parent # memos/server/
105
+ pkg_root = here.parent # memos/
106
+ repo_root = pkg_root.parent # repository root (dev layout)
107
+
108
+ # Layout 1: source checkout with a working repo-level better-sqlite3
109
+ checkout_dist = repo_root / "dist"
110
+ if (checkout_dist / "index.js").is_file() and _better_sqlite3_in(repo_root / "node_modules"):
111
+ return checkout_dist
112
+
113
+ # Layout 2: bundled wheel SDK
114
+ bundled = pkg_root / "_js"
115
+ if not (bundled / "index.js").is_file():
116
+ raise RuntimeError(
117
+ "MemOS TypeScript SDK not found. Build it first:\n"
118
+ " npm install && npm run build\n"
119
+ "(looked for dist/index.js in the source checkout and the bundled copy)"
120
+ )
121
+
122
+ # Stage the bundled SDK into a writable runtime directory. A version
123
+ # marker keeps the staged copy in sync across package upgrades.
124
+ runtime = Path(
125
+ os.environ.get("MEMOS_RUNTIME_DIR", str(Path.home() / ".memos" / "runtime" / "js"))
126
+ )
127
+ marker = runtime / ".memos-sdk-version"
128
+ needs_copy = (
129
+ not marker.is_file()
130
+ or marker.read_text(encoding="utf-8").strip() != __version__
131
+ or not (runtime / "index.js").is_file()
132
+ )
133
+ if needs_copy:
134
+ runtime.mkdir(parents=True, exist_ok=True)
135
+ for item in bundled.iterdir():
136
+ if item.name == "node_modules":
137
+ continue
138
+ dest = runtime / item.name
139
+ if item.is_dir():
140
+ if dest.exists():
141
+ shutil.rmtree(dest)
142
+ shutil.copytree(item, dest)
143
+ else:
144
+ shutil.copy2(item, dest)
145
+ marker.write_text(__version__, encoding="utf-8")
146
+
147
+ if not _better_sqlite3_in(runtime / "node_modules"):
148
+ npm = shutil.which("npm")
149
+ if npm is None:
150
+ raise RuntimeError(
151
+ "better-sqlite3 is missing and npm was not found to install it.\n"
152
+ f" Run manually: cd {runtime} && npm install {_BETTER_SQLITE3_SPEC}"
153
+ )
154
+ # npm 11+ gates install scripts behind package.json "allowScripts".
155
+ # Without this, better-sqlite3's prebuild-install step is blocked
156
+ # and the native binding never lands. "type": "module" also keeps
157
+ # Node from reparsing the ESM bridge files.
158
+ runtime_pkg = runtime / "package.json"
159
+ if not runtime_pkg.exists():
160
+ runtime_pkg.write_text(
161
+ json.dumps(
162
+ {
163
+ "name": "memos-runtime",
164
+ "private": True,
165
+ "type": "module",
166
+ "allowScripts": {"better-sqlite3": True},
167
+ },
168
+ indent=2,
169
+ ),
170
+ encoding="utf-8",
171
+ )
172
+ print(f" First run: installing {_BETTER_SQLITE3_SPEC} into {runtime} ...")
173
+ subprocess.run(
174
+ [npm, "install", "--no-audit", "--no-fund", _BETTER_SQLITE3_SPEC],
175
+ cwd=str(runtime),
176
+ check=True,
177
+ )
178
+ return runtime
179
+
180
+
181
+ def _start_node_server() -> subprocess.Popen:
182
+ """Start the Node.js MemOS server as a subprocess."""
183
+ sdk_dir = _ensure_runtime_sdk()
184
+ bridge_script = sdk_dir / "_bridge.mjs"
185
+
186
+ # Write bridge script if it doesn't exist
187
+ if not bridge_script.exists():
188
+ bridge_script.write_text(_BRIDGE_SCRIPT, encoding="utf-8")
189
+
190
+ node_bin = _find_node_binary()
191
+ proc = subprocess.Popen(
192
+ [node_bin, str(bridge_script), MEMOS_DB_PATH],
193
+ stdin=subprocess.PIPE,
194
+ stdout=subprocess.PIPE,
195
+ stderr=subprocess.PIPE,
196
+ text=True,
197
+ )
198
+ return proc
199
+
200
+
201
+ # ---------------------------------------------------------------------------
202
+ # Lifespan — start/stop Node.js subprocess
203
+ # ---------------------------------------------------------------------------
204
+
205
+ _node_process: subprocess.Popen | None = None
206
+
207
+
208
+ @asynccontextmanager
209
+ async def lifespan(app: FastAPI):
210
+ """Application lifespan — manage the Node.js subprocess."""
211
+ global _node_process
212
+ _node_process = _start_node_server()
213
+ yield
214
+ if _node_process:
215
+ _node_process.terminate()
216
+ try:
217
+ _node_process.wait(timeout=5)
218
+ except subprocess.TimeoutExpired:
219
+ _node_process.kill()
220
+
221
+
222
+ # ---------------------------------------------------------------------------
223
+ # App factory
224
+ # ---------------------------------------------------------------------------
225
+
226
+ app = FastAPI(
227
+ title="MemOS",
228
+ description="Universal memory layer for AI agents — REST API",
229
+ version=__version__,
230
+ lifespan=lifespan,
231
+ )
232
+
233
+ app.add_middleware(
234
+ CORSMiddleware,
235
+ allow_origins=["*"],
236
+ allow_credentials=True,
237
+ allow_methods=["*"],
238
+ allow_headers=["*"],
239
+ )
240
+
241
+ app.include_router(router, prefix="/api/mem")
242
+
243
+
244
+ @app.get("/health")
245
+ async def health():
246
+ """Health check endpoint."""
247
+ return {"status": "ok", "service": "memos", "version": __version__}
248
+
249
+
250
+ # ---------------------------------------------------------------------------
251
+ # Entry point
252
+ # ---------------------------------------------------------------------------
253
+
254
+
255
+ def _safe_cli_path(path: str) -> str:
256
+ """Resolve a CLI file path and require it to stay under the current
257
+ working directory (blocks ../ and absolute-path escapes)."""
258
+ resolved = os.path.realpath(path)
259
+ base = os.path.realpath(os.getcwd())
260
+ try:
261
+ contained = os.path.commonpath([resolved, base]) == base
262
+ except ValueError:
263
+ # Different drives / unreachable roots on Windows
264
+ contained = False
265
+ if not contained:
266
+ print(f"Error: Path must stay inside the working directory: {path}")
267
+ sys.exit(1)
268
+ return resolved
269
+
270
+
271
+ def _cli_backup(output: str | None = None) -> None:
272
+ """CLI backup subcommand."""
273
+ import json
274
+ import sqlite3
275
+ from datetime import datetime
276
+
277
+ db_path = MEMOS_DB_PATH
278
+ if not os.path.exists(db_path):
279
+ print(f"Error: Database not found at {db_path}")
280
+ sys.exit(1)
281
+
282
+ timestamp = datetime.now().strftime("%Y-%m-%dT%H-%M-%S")
283
+ default_output = f"./memos-backup-{timestamp}.db"
284
+ output_path = _safe_cli_path(output or default_output)
285
+
286
+ shutil.copy2(db_path, output_path)
287
+
288
+ # Get stats
289
+ db_size = os.path.getsize(db_path)
290
+ conn = sqlite3.connect(db_path)
291
+ node_count = conn.execute("SELECT COUNT(*) FROM nodes").fetchone()[0]
292
+ edge_count = conn.execute("SELECT COUNT(*) FROM edges").fetchone()[0]
293
+ conn.close()
294
+
295
+ manifest = {
296
+ "timestamp": datetime.now().isoformat(),
297
+ "version": __version__,
298
+ "nodeCount": node_count,
299
+ "edgeCount": edge_count,
300
+ "dbSizeBytes": db_size,
301
+ }
302
+
303
+ # Rebuild the manifest path from sanitized components; basename()
304
+ # strips any directory the resolved path may still carry.
305
+ manifest_path = _safe_cli_path(
306
+ os.path.join(
307
+ os.path.dirname(output_path),
308
+ os.path.basename(output_path) + ".manifest.json",
309
+ )
310
+ )
311
+ Path(manifest_path).write_text(json.dumps(manifest, indent=2))
312
+
313
+ print(f"Backup created: {output_path}")
314
+ print(f" Nodes: {node_count}, Edges: {edge_count}")
315
+ print(f" DB size: {db_size / 1024:.1f} KB")
316
+ print(f" Manifest: {manifest_path}")
317
+
318
+
319
+ def _cli_restore(path: str) -> None:
320
+ """CLI restore subcommand."""
321
+ import json
322
+
323
+ backup_path = _safe_cli_path(path)
324
+ manifest_path = f"{backup_path}.manifest.json"
325
+
326
+ if not os.path.exists(backup_path):
327
+ print(f"Error: Backup file not found: {backup_path}")
328
+ sys.exit(1)
329
+
330
+ if os.path.exists(manifest_path):
331
+ with open(manifest_path) as f:
332
+ manifest = json.load(f)
333
+ print("Backup manifest:")
334
+ print(f" Timestamp: {manifest['timestamp']}")
335
+ print(f" Version: {manifest['version']}")
336
+ print(f" Nodes: {manifest['nodeCount']}, Edges: {manifest['edgeCount']}")
337
+
338
+ db_path = MEMOS_DB_PATH
339
+ db_dir = os.path.dirname(db_path)
340
+ if db_dir and not os.path.exists(db_dir):
341
+ os.makedirs(db_dir, exist_ok=True)
342
+
343
+ shutil.copy2(backup_path, db_path)
344
+
345
+ # Report restored state
346
+ import sqlite3
347
+
348
+ conn = sqlite3.connect(db_path)
349
+ node_count = conn.execute("SELECT COUNT(*) FROM nodes").fetchone()[0]
350
+ edge_count = conn.execute("SELECT COUNT(*) FROM edges").fetchone()[0]
351
+ conn.close()
352
+
353
+ print(f"\nRestored from: {backup_path}")
354
+ print(f" Nodes: {node_count}, Edges: {edge_count}")
355
+
356
+
357
+ def main():
358
+ """Run the MemOS server with uvicorn, or handle CLI subcommands."""
359
+ # Handle CLI subcommands
360
+ if len(sys.argv) > 1:
361
+ subcmd = sys.argv[1]
362
+ if subcmd == "backup":
363
+ output = None
364
+ if "--output" in sys.argv:
365
+ idx = sys.argv.index("--output")
366
+ if idx + 1 < len(sys.argv):
367
+ output = sys.argv[idx + 1]
368
+ _cli_backup(output)
369
+ return
370
+ elif subcmd == "restore":
371
+ if len(sys.argv) < 3:
372
+ print(
373
+ "Error: restore requires a path argument.\n Usage: memos-server restore <path>"
374
+ )
375
+ sys.exit(1)
376
+ _cli_restore(sys.argv[2])
377
+ return
378
+
379
+ import uvicorn
380
+
381
+ print(f" MemOS server starting on {MEMOS_HOST}:{MEMOS_PORT}")
382
+ print(f" Database: {MEMOS_DB_PATH}")
383
+ print(f" Docs: http://{MEMOS_HOST}:{MEMOS_PORT}/docs")
384
+
385
+ uvicorn.run(
386
+ "memos.server.main:app",
387
+ host=MEMOS_HOST,
388
+ port=MEMOS_PORT,
389
+ log_level=MEMOS_LOG_LEVEL,
390
+ reload=False,
391
+ )
392
+
393
+
394
+ if __name__ == "__main__":
395
+ main()
396
+
397
+
398
+ # ---------------------------------------------------------------------------
399
+ # Node.js bridge script (inline, written to disk on first run)
400
+ # ---------------------------------------------------------------------------
401
+
402
+ _BRIDGE_SCRIPT = r"""/**
403
+ * Node.js bridge — reads JSON-RPC messages from stdin, executes
404
+ * MemOS operations, and writes results to stdout.
405
+ *
406
+ * This allows the Python server to call the TypeScript SDK
407
+ * without an HTTP round-trip.
408
+ */
409
+
410
+ import { MemOS } from './index.js';
411
+
412
+ const dbPath = process.argv[2] || undefined;
413
+
414
+ // Embedding configuration via MEMOS_EMBEDDING_* environment variables.
415
+ // Without MEMOS_EMBEDDING_PROVIDER the bridge falls back to the SDK
416
+ // default (local hash), keeping zero-config behavior for plain installs.
417
+ const env = process.env;
418
+ const memosConfig = { dbPath };
419
+ if (env.MEMOS_EMBEDDING_PROVIDER) {
420
+ const dimensions = env.MEMOS_EMBEDDING_DIMENSIONS;
421
+ memosConfig.experimental = { semanticSearch: true };
422
+ memosConfig.embeddings = {
423
+ enabled: true,
424
+ provider: env.MEMOS_EMBEDDING_PROVIDER,
425
+ ...(env.MEMOS_EMBEDDING_MODEL ? { model: env.MEMOS_EMBEDDING_MODEL } : {}),
426
+ ...(env.MEMOS_EMBEDDING_BASE_URL ? { baseUrl: env.MEMOS_EMBEDDING_BASE_URL } : {}),
427
+ ...(env.MEMOS_EMBEDDING_API_KEY ? { apiKey: env.MEMOS_EMBEDDING_API_KEY } : {}),
428
+ ...(dimensions ? { dimensions: parseInt(dimensions, 10) } : {}),
429
+ ...(env.MEMOS_EMBEDDING_QUERY_PREFIX ? { queryPrefix: env.MEMOS_EMBEDDING_QUERY_PREFIX } : {}),
430
+ ...(env.MEMOS_EMBEDDING_DOCUMENT_PREFIX ? { documentPrefix: env.MEMOS_EMBEDDING_DOCUMENT_PREFIX } : {}),
431
+ };
432
+ if (env.MEMOS_EMBEDDING_BATCH_SIZE) {
433
+ memosConfig.embeddingQueue = {
434
+ batchSize: parseInt(env.MEMOS_EMBEDDING_BATCH_SIZE, 10),
435
+ };
436
+ }
437
+ }
438
+
439
+ const memos = new MemOS(memosConfig);
440
+ await memos.init();
441
+
442
+ const rl = (await import('readline')).createInterface({
443
+ input: process.stdin,
444
+ output: process.stdout,
445
+ terminal: false,
446
+ });
447
+
448
+ rl.on('line', async (line) => {
449
+ let msg;
450
+ try {
451
+ msg = JSON.parse(line);
452
+ } catch {
453
+ writeResponse({ error: 'Invalid JSON' });
454
+ return;
455
+ }
456
+
457
+ const { id, method, params } = msg;
458
+
459
+ try {
460
+ let result;
461
+ switch (method) {
462
+ case 'store':
463
+ result = await memos.store(params.content, params.opts || {});
464
+ break;
465
+ case 'retrieve':
466
+ result = await memos.retrieve(params.id);
467
+ break;
468
+ case 'search':
469
+ result = await memos.search(params.filter || params.query);
470
+ break;
471
+ case 'forget':
472
+ result = await memos.forget(params.id);
473
+ break;
474
+ case 'summarize':
475
+ result = await memos.summarize();
476
+ break;
477
+ case 'link':
478
+ result = await memos.link(
479
+ params.sourceId,
480
+ params.targetId,
481
+ params.relation,
482
+ params.weight
483
+ );
484
+ break;
485
+ case 'graph':
486
+ result = await memos.getGraph();
487
+ break;
488
+ case 'neighbours':
489
+ result = await memos.getNeighbours(params.nodeId);
490
+ break;
491
+ case 'edges':
492
+ result = await memos.getEdges(params.nodeId);
493
+ break;
494
+ case 'count':
495
+ result = memos.count;
496
+ break;
497
+ case 'setTTL':
498
+ await memos.setTTL(params.id, params.seconds);
499
+ result = true;
500
+ break;
501
+ case 'clearTTL':
502
+ await memos.clearTTL(params.id);
503
+ result = true;
504
+ break;
505
+ case 'tag':
506
+ await memos.tag(params.id, params.tags);
507
+ result = true;
508
+ break;
509
+ case 'untag':
510
+ await memos.untag(params.id, params.tags);
511
+ result = true;
512
+ break;
513
+ case 'listByTag':
514
+ result = await memos.listByTag(params.tag);
515
+ break;
516
+ case 'export':
517
+ result = await memos.export(params.opts || {});
518
+ break;
519
+ case 'semanticSearch':
520
+ result = await memos.semanticSearch(params.query, params.limit, params.threshold);
521
+ break;
522
+ case 'graphViz':
523
+ result = await memos.graphViz();
524
+ break;
525
+ case 'listNamespaces':
526
+ result = await memos.listNamespaces();
527
+ break;
528
+ case 'namespaceCount':
529
+ result = await memos.namespaceCount(params.namespace);
530
+ break;
531
+ case 'injectContext':
532
+ result = await memos.injectContext(params.id, params.depth, params.maxChars);
533
+ break;
534
+ default:
535
+ writeResponse({ id, error: `Unknown method: ${method}` });
536
+ return;
537
+ }
538
+ writeResponse({ id, result });
539
+ } catch (err) {
540
+ writeResponse({ id, error: err.message || String(err) });
541
+ }
542
+ });
543
+
544
+ function writeResponse(obj) {
545
+ process.stdout.write(JSON.stringify(obj) + '\n');
546
+ }
547
+ """