unibaseio-membase 0.3.0__tar.gz

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.
@@ -0,0 +1,54 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+
23
+ # Virtual Environment
24
+ venv/
25
+ ENV/
26
+ env/
27
+ .env/
28
+ .venv/
29
+ .env
30
+
31
+ # IDE
32
+ .idea/
33
+ .vscode/
34
+ *.swp
35
+ *.swo
36
+
37
+ # Logs
38
+ *.log
39
+ logs/
40
+ log/
41
+
42
+ # OS specific
43
+ .DS_Store
44
+ Thumbs.db
45
+
46
+ chroma_db/
47
+
48
+ out/.pytest_cache/
49
+ .ruff_cache/
50
+ *.db
51
+
52
+ typescript/dist/
53
+ node_modules/
54
+ cli/dist/
@@ -0,0 +1,33 @@
1
+ Membase SDK — Proprietary License
2
+
3
+ Copyright (c) 2026 Unibase. All rights reserved.
4
+
5
+ This software, including its source code, packages, documentation and assets (the "Software"),
6
+ is proprietary to Unibase and is licensed, not sold.
7
+
8
+ 1. Use. Unibase grants you a non-exclusive, non-transferable, revocable license to install and
9
+ use the Software as published by Unibase (the `unibaseio-membase` package on PyPI and the
10
+ `@unibaseio/membase` and `@unibaseio/membase-cli` packages on npm), and to
11
+ include it as a dependency of your own applications, to access Membase services and the
12
+ Membase engine.
13
+
14
+ 2. Restrictions. Except as expressly permitted by this license or required by applicable law,
15
+ you may not: (a) redistribute, sublicense, sell, rent, lease, or otherwise make the Software
16
+ available to third parties on its own; (b) modify or create derivative works of the Software
17
+ other than for your own internal use; or (c) remove or alter any proprietary notices.
18
+
19
+ 3. Third-party components. Third-party components included with the Software are licensed
20
+ under their own terms, which govern those components.
21
+
22
+ 4. No warranty. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
24
+ PARTICULAR PURPOSE AND NONINFRINGEMENT.
25
+
26
+ 5. Limitation of liability. IN NO EVENT SHALL UNIBASE BE LIABLE FOR ANY CLAIM, DAMAGES OR
27
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
28
+ OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29
+
30
+ 6. Termination. This license terminates automatically if you breach it. On termination you
31
+ must stop using and delete all copies of the Software.
32
+
33
+ For other licensing, contact Unibase.
@@ -0,0 +1,154 @@
1
+ Metadata-Version: 2.4
2
+ Name: unibaseio-membase
3
+ Version: 0.3.0
4
+ Summary: Membase SDK: drop-in memory infrastructure for AI agents and apps, hosted or on your machine. Python client, CLI and MCP server.
5
+ Project-URL: Homepage, https://www.unibase.com/memory
6
+ Project-URL: Documentation, https://docs.membase.ai
7
+ Project-URL: Repository, https://github.com/unibaseio/membase-ai
8
+ Author: Unibase
9
+ License: Proprietary
10
+ License-File: LICENSE
11
+ Keywords: agent-memory,agents,ai,llm,llm-memory,long-term-memory,mcp,membase,memory
12
+ Classifier: License :: Other/Proprietary License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Classifier: Typing :: Typed
20
+ Requires-Python: >=3.10
21
+ Requires-Dist: httpx<1,>=0.25
22
+ Provides-Extra: local
23
+ Requires-Dist: mcp<2,>=1.0; extra == 'local'
24
+ Requires-Dist: unibaseio-membase-core[all]>=0.3.0; (python_version >= '3.12') and extra == 'local'
25
+ Description-Content-Type: text/markdown
26
+
27
+ <p align="center">
28
+ <img src="https://raw.githubusercontent.com/unibaseio/membase-ai/main/assets/membase-logo.png" width="72" alt="Membase">
29
+ </p>
30
+
31
+ <h1 align="center">Membase</h1>
32
+
33
+ <p align="center">
34
+ <b>One memory. Every AI.</b><br>
35
+ Teach it once. Every AI remembers.
36
+ </p>
37
+
38
+ <p align="center">
39
+ <a href="https://pypi.org/project/unibaseio-membase/"><img src="https://img.shields.io/pypi/v/unibaseio-membase?label=pypi" alt="PyPI"></a>
40
+ <a href="https://www.npmjs.com/package/@unibaseio/membase"><img src="https://img.shields.io/npm/v/@unibaseio/membase?label=npm" alt="npm"></a>
41
+ <a href="https://github.com/unibaseio/membase-ai/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-proprietary-blue" alt="Proprietary"></a>
42
+ </p>
43
+
44
+ <p align="center">
45
+ <a href="https://www.unibase.com/memory">Website</a> ·
46
+ <a href="https://docs.membase.ai">Docs</a> ·
47
+ <a href="https://www.app.membase.ai">Web app</a> ·
48
+ <a href="https://discord.gg/nB9EfPGsSf">Discord</a> ·
49
+ <a href="https://x.com/Unibase_AI">X</a>
50
+ </p>
51
+
52
+ <p align="center">
53
+ <img src="https://raw.githubusercontent.com/unibaseio/membase-ai/main/assets/hero.svg" width="840" alt="Add sources, manage one memory, use it from every AI and app">
54
+ </p>
55
+
56
+ Membase keeps one memory for everything you and your agents know, and every AI you use reads it.
57
+ You stop re-explaining yourself each time you switch models or tools.
58
+
59
+ - **Add.** Facts, documents, web pages and chat exports from Claude or ChatGPT go into one memory.
60
+ - **Manage.** Containers keep projects apart; search it, ask it questions, forget what should go.
61
+ - **Use.** Your code, Claude Code, Cursor, Codex and ChatGPT read the same memory through the SDK or MCP.
62
+
63
+ This repository is the developer side: Python and TypeScript clients, the `membase` command line
64
+ and an MCP server. The memory lives in your Membase account, which the
65
+ [web app](https://www.app.membase.ai) and browser extension also fill, or on your machine with
66
+ `unibaseio-membase[local]`.
67
+
68
+ ## Write once, read from any AI
69
+
70
+ <p align="center">
71
+ <img src="https://raw.githubusercontent.com/unibaseio/membase-ai/main/assets/in-action.svg" width="840" alt="Your app writes a memory; any AI reads it, here Claude Code">
72
+ </p>
73
+
74
+ Recordings: [Python SDK](https://github.com/unibaseio/membase-ai/blob/main/assets/videos/sdk.mp4) (57 s) ·
75
+ [Claude Code](https://github.com/unibaseio/membase-ai/blob/main/assets/videos/switch-ai.mp4) (1 min 33 s). The answer above is illustrative.
76
+
77
+ ## Accuracy
78
+
79
+ <p align="center">
80
+ <img src="https://raw.githubusercontent.com/unibaseio/membase-ai/main/assets/results.svg" width="840" alt="LoCoMo 93.1%, LongMemEval_S 92.6%, DMR 92.2%">
81
+ </p>
82
+
83
+ On three public long-term memory benchmarks, with a few thousand tokens of context per question.
84
+
85
+ ## Quickstart
86
+
87
+ <p align="center">
88
+ <img src="https://raw.githubusercontent.com/unibaseio/membase-ai/main/assets/architecture.svg" width="840" alt="Hosted, local in process, or local over HTTP">
89
+ </p>
90
+
91
+ ```bash
92
+ pip install unibaseio-membase # hosted · Python 3.10+ · needs MEMBASE_API_KEY
93
+ pip install 'unibaseio-membase[local]' # local · Python 3.12 or 3.13 · needs OPENAI_API_KEY
94
+ npm install @unibaseio/membase # TypeScript · Node 18+
95
+ npm install -g @unibaseio/membase-cli # the `membase` command (hosted) without Python
96
+ ```
97
+
98
+ ```python
99
+ from membase import Membase
100
+
101
+ m = Membase() # hosted
102
+ # m = Membase(local=True) # local, in ~/.membase
103
+
104
+ m.memories.add("We picked Postgres for the ledger service.", container="Engineering")
105
+ m.search("what database is the ledger on?")
106
+ m.add("Design notes …", container="Engineering", custom_id="design-1") # a document
107
+ m.ask("Which database did we choose for the ledger?")
108
+ ```
109
+
110
+ Every method, its REST route and MCP tool, and the access it needs: [SDK reference](https://docs.membase.ai/build/reference/sdk-quickstart/).
111
+ The Python package is `unibaseio-membase` (import `membase`); `membase` on PyPI is unrelated.
112
+
113
+ ## Command line
114
+
115
+ ```bash
116
+ membase --local add "We picked Postgres for the ledger" --container Engineering
117
+ membase --local search "what did we pick for the ledger?"
118
+ membase --local ask "Which database is the ledger on?"
119
+ membase --local import ~/Downloads/claude-export.json # Claude, ChatGPT, markdown or JSON chats
120
+ membase --local agent ingest trace.json --agent coder
121
+ membase --local agent search "fix flaky deploy" --agent coder
122
+ ```
123
+
124
+ Without `--local` (or `--store DIR`, both before the command) commands use the hosted API; `agent`,
125
+ `import` and `serve` need `unibaseio-membase[local]`.
126
+
127
+ ## MCP
128
+
129
+ <p align="center">
130
+ <img src="https://raw.githubusercontent.com/unibaseio/membase-ai/main/assets/mcp.svg" width="840" alt="MCP clients, hosted or local server, same tools">
131
+ </p>
132
+
133
+ ```bash
134
+ claude mcp add membase -- membase --local mcp # local
135
+ claude mcp add --transport http membase https://api.app.membase.io/mcp-http \
136
+ --header "Authorization: Bearer $MEMBASE_API_KEY" # hosted
137
+ ```
138
+
139
+ Hosted without the header, the connection signs you in and is read-only. Setup for Claude, ChatGPT,
140
+ Cursor, Codex and other clients: [Connect your AI](https://docs.membase.ai/connect/).
141
+
142
+ ## Docs
143
+
144
+ - [SDK reference](https://docs.membase.ai/build/reference/sdk-quickstart/): methods, REST routes, MCP tools, access levels
145
+ - [Memory operations](https://docs.membase.ai/build/guides/memory-operations/): containers, documents, memories, profile, ask
146
+ - [Authentication](https://docs.membase.ai/build/reference/authentication/): keys and what each one can reach
147
+ - [Connect your AI](https://docs.membase.ai/connect/): per-client MCP setup
148
+ - [Local engine](https://github.com/unibaseio/membase-ai/blob/main/docs/reference.md): what it stores, `membase serve`, settings
149
+
150
+ ## License
151
+
152
+ Copyright © 2026 Unibase. All rights reserved. Proprietary; see
153
+ [LICENSE](https://github.com/unibaseio/membase-ai/blob/main/LICENSE). `unibaseio-membase[local]` also
154
+ installs unibaseio-membase-core, the compiled engine, under its own Unibase license.
@@ -0,0 +1,128 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/unibaseio/membase-ai/main/assets/membase-logo.png" width="72" alt="Membase">
3
+ </p>
4
+
5
+ <h1 align="center">Membase</h1>
6
+
7
+ <p align="center">
8
+ <b>One memory. Every AI.</b><br>
9
+ Teach it once. Every AI remembers.
10
+ </p>
11
+
12
+ <p align="center">
13
+ <a href="https://pypi.org/project/unibaseio-membase/"><img src="https://img.shields.io/pypi/v/unibaseio-membase?label=pypi" alt="PyPI"></a>
14
+ <a href="https://www.npmjs.com/package/@unibaseio/membase"><img src="https://img.shields.io/npm/v/@unibaseio/membase?label=npm" alt="npm"></a>
15
+ <a href="https://github.com/unibaseio/membase-ai/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-proprietary-blue" alt="Proprietary"></a>
16
+ </p>
17
+
18
+ <p align="center">
19
+ <a href="https://www.unibase.com/memory">Website</a> ·
20
+ <a href="https://docs.membase.ai">Docs</a> ·
21
+ <a href="https://www.app.membase.ai">Web app</a> ·
22
+ <a href="https://discord.gg/nB9EfPGsSf">Discord</a> ·
23
+ <a href="https://x.com/Unibase_AI">X</a>
24
+ </p>
25
+
26
+ <p align="center">
27
+ <img src="https://raw.githubusercontent.com/unibaseio/membase-ai/main/assets/hero.svg" width="840" alt="Add sources, manage one memory, use it from every AI and app">
28
+ </p>
29
+
30
+ Membase keeps one memory for everything you and your agents know, and every AI you use reads it.
31
+ You stop re-explaining yourself each time you switch models or tools.
32
+
33
+ - **Add.** Facts, documents, web pages and chat exports from Claude or ChatGPT go into one memory.
34
+ - **Manage.** Containers keep projects apart; search it, ask it questions, forget what should go.
35
+ - **Use.** Your code, Claude Code, Cursor, Codex and ChatGPT read the same memory through the SDK or MCP.
36
+
37
+ This repository is the developer side: Python and TypeScript clients, the `membase` command line
38
+ and an MCP server. The memory lives in your Membase account, which the
39
+ [web app](https://www.app.membase.ai) and browser extension also fill, or on your machine with
40
+ `unibaseio-membase[local]`.
41
+
42
+ ## Write once, read from any AI
43
+
44
+ <p align="center">
45
+ <img src="https://raw.githubusercontent.com/unibaseio/membase-ai/main/assets/in-action.svg" width="840" alt="Your app writes a memory; any AI reads it, here Claude Code">
46
+ </p>
47
+
48
+ Recordings: [Python SDK](https://github.com/unibaseio/membase-ai/blob/main/assets/videos/sdk.mp4) (57 s) ·
49
+ [Claude Code](https://github.com/unibaseio/membase-ai/blob/main/assets/videos/switch-ai.mp4) (1 min 33 s). The answer above is illustrative.
50
+
51
+ ## Accuracy
52
+
53
+ <p align="center">
54
+ <img src="https://raw.githubusercontent.com/unibaseio/membase-ai/main/assets/results.svg" width="840" alt="LoCoMo 93.1%, LongMemEval_S 92.6%, DMR 92.2%">
55
+ </p>
56
+
57
+ On three public long-term memory benchmarks, with a few thousand tokens of context per question.
58
+
59
+ ## Quickstart
60
+
61
+ <p align="center">
62
+ <img src="https://raw.githubusercontent.com/unibaseio/membase-ai/main/assets/architecture.svg" width="840" alt="Hosted, local in process, or local over HTTP">
63
+ </p>
64
+
65
+ ```bash
66
+ pip install unibaseio-membase # hosted · Python 3.10+ · needs MEMBASE_API_KEY
67
+ pip install 'unibaseio-membase[local]' # local · Python 3.12 or 3.13 · needs OPENAI_API_KEY
68
+ npm install @unibaseio/membase # TypeScript · Node 18+
69
+ npm install -g @unibaseio/membase-cli # the `membase` command (hosted) without Python
70
+ ```
71
+
72
+ ```python
73
+ from membase import Membase
74
+
75
+ m = Membase() # hosted
76
+ # m = Membase(local=True) # local, in ~/.membase
77
+
78
+ m.memories.add("We picked Postgres for the ledger service.", container="Engineering")
79
+ m.search("what database is the ledger on?")
80
+ m.add("Design notes …", container="Engineering", custom_id="design-1") # a document
81
+ m.ask("Which database did we choose for the ledger?")
82
+ ```
83
+
84
+ Every method, its REST route and MCP tool, and the access it needs: [SDK reference](https://docs.membase.ai/build/reference/sdk-quickstart/).
85
+ The Python package is `unibaseio-membase` (import `membase`); `membase` on PyPI is unrelated.
86
+
87
+ ## Command line
88
+
89
+ ```bash
90
+ membase --local add "We picked Postgres for the ledger" --container Engineering
91
+ membase --local search "what did we pick for the ledger?"
92
+ membase --local ask "Which database is the ledger on?"
93
+ membase --local import ~/Downloads/claude-export.json # Claude, ChatGPT, markdown or JSON chats
94
+ membase --local agent ingest trace.json --agent coder
95
+ membase --local agent search "fix flaky deploy" --agent coder
96
+ ```
97
+
98
+ Without `--local` (or `--store DIR`, both before the command) commands use the hosted API; `agent`,
99
+ `import` and `serve` need `unibaseio-membase[local]`.
100
+
101
+ ## MCP
102
+
103
+ <p align="center">
104
+ <img src="https://raw.githubusercontent.com/unibaseio/membase-ai/main/assets/mcp.svg" width="840" alt="MCP clients, hosted or local server, same tools">
105
+ </p>
106
+
107
+ ```bash
108
+ claude mcp add membase -- membase --local mcp # local
109
+ claude mcp add --transport http membase https://api.app.membase.io/mcp-http \
110
+ --header "Authorization: Bearer $MEMBASE_API_KEY" # hosted
111
+ ```
112
+
113
+ Hosted without the header, the connection signs you in and is read-only. Setup for Claude, ChatGPT,
114
+ Cursor, Codex and other clients: [Connect your AI](https://docs.membase.ai/connect/).
115
+
116
+ ## Docs
117
+
118
+ - [SDK reference](https://docs.membase.ai/build/reference/sdk-quickstart/): methods, REST routes, MCP tools, access levels
119
+ - [Memory operations](https://docs.membase.ai/build/guides/memory-operations/): containers, documents, memories, profile, ask
120
+ - [Authentication](https://docs.membase.ai/build/reference/authentication/): keys and what each one can reach
121
+ - [Connect your AI](https://docs.membase.ai/connect/): per-client MCP setup
122
+ - [Local engine](https://github.com/unibaseio/membase-ai/blob/main/docs/reference.md): what it stores, `membase serve`, settings
123
+
124
+ ## License
125
+
126
+ Copyright © 2026 Unibase. All rights reserved. Proprietary; see
127
+ [LICENSE](https://github.com/unibaseio/membase-ai/blob/main/LICENSE). `unibaseio-membase[local]` also
128
+ installs unibaseio-membase-core, the compiled engine, under its own Unibase license.
@@ -0,0 +1,38 @@
1
+ """Membase — long-term memory for AI."""
2
+
3
+ from ._version import __version__
4
+ from .client import DEFAULT_BASE_URL, Membase
5
+ from .requirements import MissingDependencyError
6
+ from .errors import (
7
+ APIConnectionError,
8
+ APIStatusError,
9
+ APITimeoutError,
10
+ AuthenticationError,
11
+ BadRequestError,
12
+ ConflictError,
13
+ InternalServerError,
14
+ MembaseError,
15
+ NotFoundError,
16
+ PermissionDeniedError,
17
+ RateLimitError,
18
+ UnprocessableEntityError,
19
+ )
20
+
21
+ __all__ = [
22
+ "DEFAULT_BASE_URL",
23
+ "Membase",
24
+ "MembaseError",
25
+ "MissingDependencyError",
26
+ "APIConnectionError",
27
+ "APITimeoutError",
28
+ "APIStatusError",
29
+ "BadRequestError",
30
+ "AuthenticationError",
31
+ "PermissionDeniedError",
32
+ "NotFoundError",
33
+ "ConflictError",
34
+ "UnprocessableEntityError",
35
+ "RateLimitError",
36
+ "InternalServerError",
37
+ "__version__",
38
+ ]
@@ -0,0 +1,3 @@
1
+ from .cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1 @@
1
+ __version__ = "0.3.0"
@@ -0,0 +1,246 @@
1
+ """``membase``: the Membase command line, hosted or local."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from ._version import __version__
12
+ from .client import Membase
13
+ from .errors import MembaseError
14
+
15
+
16
+ def _client(args: argparse.Namespace) -> Membase:
17
+ if args.local or args.store:
18
+ return Membase(local=args.store or True)
19
+ try:
20
+ return Membase(api_key=args.api_key, base_url=args.base_url)
21
+ except ValueError:
22
+ raise SystemExit("no API key: pass --api-key or set MEMBASE_API_KEY (Connect › Developer keys), "
23
+ "or use --local") from None
24
+
25
+
26
+ def _print(obj: Any) -> None:
27
+ print(json.dumps(obj, ensure_ascii=False, indent=2, default=str))
28
+
29
+
30
+ _IMPORTABLE = {".json", ".md", ".markdown", ".txt"}
31
+
32
+
33
+ def _import_files(paths: list[str]) -> list[Path]:
34
+ out: list[Path] = []
35
+ for raw in paths:
36
+ p = Path(raw).expanduser()
37
+ if p.is_dir():
38
+ out += sorted(f for f in p.rglob("*") if f.is_file() and f.suffix.lower() in _IMPORTABLE)
39
+ elif p.is_file():
40
+ out.append(p)
41
+ else:
42
+ raise SystemExit(f"no such file or directory: {raw}")
43
+ return out
44
+
45
+
46
+ def _cmd_import(args: argparse.Namespace, m: Membase) -> Any:
47
+ try:
48
+ from membase_core.sources import detect_format, load_sessions
49
+ except ImportError:
50
+ raise SystemExit("membase import reads chat exports with the local engine: pip install 'unibaseio-membase[local]'")
51
+ sessions: list[dict] = []
52
+ found: list[dict] = []
53
+ for f in _import_files(args.files):
54
+ got = load_sessions(f)
55
+ sessions += got
56
+ found.append({"file": str(f), "format": detect_format(f), "sessions": len(got)})
57
+ if not sessions:
58
+ raise SystemExit("no sessions found")
59
+ if args.dry_run:
60
+ return {"would_import": len(sessions), "files": found}
61
+ transport = m._http._transport # noqa: SLF001 - the local transport, when local
62
+ backend = getattr(transport, "backend", None)
63
+ if backend is not None:
64
+ return backend.import_sessions(sessions, args.container)
65
+ out = []
66
+ for s in sessions:
67
+ text = "\n".join(f"{t['role']}: {t['content']}" for t in s["turns"])
68
+ out.append(m.add(text, container=args.container, title=s["session_id"],
69
+ custom_id=f"session:{s['session_id']}"))
70
+ return {"sessions": len(out), "documents": out}
71
+
72
+
73
+ def _load_trace(path: str) -> tuple[list[dict], dict]:
74
+ raw = sys.stdin.read() if path == "-" else Path(path).expanduser().read_text(encoding="utf-8")
75
+ data = json.loads(raw)
76
+ if isinstance(data, list):
77
+ return data, {}
78
+ if isinstance(data, dict) and isinstance(data.get("messages"), list):
79
+ return data["messages"], {k: data[k] for k in ("agent_id", "session_id") if data.get(k)}
80
+ raise SystemExit("a trace is a JSON list of messages or an object with a 'messages' list")
81
+
82
+
83
+ def _cmd_agent(args: argparse.Namespace, m: Membase) -> Any:
84
+ backend = getattr(m._http._transport, "backend", None) # noqa: SLF001
85
+ if backend is None:
86
+ raise SystemExit("agent memory is local: use membase --local agent ...")
87
+ if args.agent_cmd == "ingest":
88
+ messages, meta = _load_trace(args.trace)
89
+ session = args.session or meta.get("session_id") or (Path(args.trace).stem if args.trace != "-" else None)
90
+ return backend.agent_ingest(messages, agent_id=args.agent, session_id=session, container=args.container)
91
+ if args.agent_cmd == "search":
92
+ return backend.agent_search(args.q, agent_id=args.agent, kind=args.kind, limit=args.limit,
93
+ container=args.container)
94
+ return backend.agent_skills(args.agent, container=args.container)
95
+
96
+
97
+ def _parser() -> argparse.ArgumentParser:
98
+ p = argparse.ArgumentParser(prog="membase", description="Membase — long-term memory for AI.")
99
+ p.add_argument("--version", action="version", version=__version__)
100
+ p.add_argument("--local", action="store_true",
101
+ help="use local memory (store ~/.membase) instead of the hosted API")
102
+ p.add_argument("--store", default=None, metavar="DIR",
103
+ help="local store directory (default ~/.membase); implies --local")
104
+ p.add_argument("--api-key", default=None, help="hosted API key (default MEMBASE_API_KEY)")
105
+ p.add_argument("--base-url", default=None, help="hosted API base URL (default MEMBASE_BASE_URL)")
106
+ sub = p.add_subparsers(dest="cmd", required=True)
107
+
108
+ sub.add_parser("containers", help="list the containers in reach")
109
+
110
+ s = sub.add_parser("search", help="search memory")
111
+ s.add_argument("q")
112
+ s.add_argument("--container", help="one container id (default: every container in reach)")
113
+ s.add_argument("--limit", type=int, help="passages in all, 1-50")
114
+
115
+ a = sub.add_parser("add", help="remember one fact or a short exchange")
116
+ a.add_argument("content")
117
+ a.add_argument("--container", help="container id (may be omitted when one is in reach)")
118
+ a.add_argument("--static", action="store_true", help="a standing fact about the user (profile)")
119
+ a.add_argument("--title", default="")
120
+
121
+ d = sub.add_parser("documents", help="documents: add / list / get / delete")
122
+ dsub = d.add_subparsers(dest="doc_cmd", required=True)
123
+ da = dsub.add_parser("add", help="give a container raw material: a file, --url or --text")
124
+ da.add_argument("path", nargs="?")
125
+ da.add_argument("--url", help="a public web address to fetch")
126
+ da.add_argument("--text", help="the document's text")
127
+ da.add_argument("--container", default=None, help="container id (default: default)")
128
+ da.add_argument("--title", default="")
129
+ da.add_argument("--custom-id", default="", help="your own id; the same id again is a no-op")
130
+ dl = dsub.add_parser("list", help="documents, newest first")
131
+ dl.add_argument("--container")
132
+ dg = dsub.add_parser("get", help="one document, with whether it is learned")
133
+ dg.add_argument("document_id")
134
+ dd = dsub.add_parser("delete", help="remove a document")
135
+ dd.add_argument("document_id")
136
+ dd.add_argument("--confirm", action="store_true", help="remove it; without this, only show what would go")
137
+
138
+ f = sub.add_parser("forget", help="forget one memory by id")
139
+ f.add_argument("memory_id")
140
+ f.add_argument("--container")
141
+ f.add_argument("--confirm", action="store_true", help="forget it; without this, only show what would go")
142
+
143
+ pr = sub.add_parser("profile", help="who the user is")
144
+ pr.add_argument("q", nargs="?")
145
+
146
+ k = sub.add_parser("ask", help="an answer composed from memory")
147
+ k.add_argument("message")
148
+
149
+ i = sub.add_parser("import", help="import chat exports (Claude, ChatGPT, markdown, JSON); directories are scanned")
150
+ i.add_argument("files", nargs="+")
151
+ i.add_argument("--container")
152
+ i.add_argument("--dry-run", action="store_true", help="only report what would be imported")
153
+
154
+ g = sub.add_parser("agent", help="agent memory (local): traces to cases and skills")
155
+ gsub = g.add_subparsers(dest="agent_cmd", required=True)
156
+ gi = gsub.add_parser("ingest", help="learn from an agent trace (chat-completions JSON, or '-' for stdin)")
157
+ gi.add_argument("trace")
158
+ gi.add_argument("--agent", required=True, help="the agent's id (owner of its cases and skills)")
159
+ gi.add_argument("--session", default=None)
160
+ gi.add_argument("--container")
161
+ gs = gsub.add_parser("search", help="search an agent's cases and skills")
162
+ gs.add_argument("q")
163
+ gs.add_argument("--agent", required=True)
164
+ gs.add_argument("--kind", choices=("cases", "skills", "both"), default="both")
165
+ gs.add_argument("--limit", type=int, default=10)
166
+ gs.add_argument("--container")
167
+ gk = gsub.add_parser("skills", help="list an agent's skills")
168
+ gk.add_argument("--agent", required=True)
169
+ gk.add_argument("--container")
170
+
171
+ sv = sub.add_parser("serve", help="serve local memory over HTTP on the /v1 routes (always local; "
172
+ "MEMBASE_LOCAL_TOKEN requires a bearer)")
173
+ sv.add_argument("--host", default="127.0.0.1", help="default 127.0.0.1")
174
+ sv.add_argument("--port", type=int, default=8787, help="default 8787")
175
+
176
+ mc = sub.add_parser("mcp", help="run the MCP server (hosted, or local with --local)")
177
+ mc.add_argument("--transport", choices=("stdio", "sse", "streamable-http"), default="stdio")
178
+ mc.add_argument("--host", default="127.0.0.1", help="default 127.0.0.1 (sse / streamable-http)")
179
+ mc.add_argument("--port", type=int, default=8765, help="default 8765 (sse / streamable-http)")
180
+ return p
181
+
182
+
183
+ def main(argv: list[str] | None = None) -> int:
184
+ args = _parser().parse_args(argv)
185
+ try:
186
+ if args.cmd == "serve":
187
+ from .local.server import serve
188
+
189
+ serve(args.host, args.port, root=args.store)
190
+ return 0
191
+ if args.cmd == "mcp":
192
+ from .requirements import require_mcp
193
+
194
+ require_mcp()
195
+ m = _client(args)
196
+ if args.cmd == "mcp":
197
+ from .mcp import run
198
+
199
+ run(m, transport=args.transport, host=args.host, port=args.port)
200
+ return 0
201
+ if args.cmd == "containers":
202
+ out = m.containers.list()
203
+ elif args.cmd == "search":
204
+ out = m.search(args.q, container=args.container, limit=args.limit)
205
+ elif args.cmd == "add":
206
+ out = m.memories.add(args.content, container=args.container, static=args.static, title=args.title)
207
+ elif args.cmd == "documents":
208
+ if args.doc_cmd == "add":
209
+ if args.path:
210
+ content = Path(args.path).read_text()
211
+ title = args.title or Path(args.path).stem
212
+ else:
213
+ content, title = args.text, args.title
214
+ if (content is None) == (args.url is None):
215
+ raise SystemExit("documents add needs exactly one of PATH, --text or --url")
216
+ out = m.add(content, container=args.container or "default", url=args.url,
217
+ title=title, custom_id=args.custom_id)
218
+ elif args.doc_cmd == "list":
219
+ out = m.documents.list(container=args.container)
220
+ elif args.doc_cmd == "get":
221
+ out = m.documents.get(args.document_id)
222
+ else:
223
+ out = m.documents.delete(args.document_id, confirm=args.confirm)
224
+ elif args.cmd == "forget":
225
+ out = m.memories.forget(args.memory_id, container=args.container, confirm=args.confirm)
226
+ elif args.cmd == "profile":
227
+ out = m.profile(args.q)
228
+ elif args.cmd == "ask":
229
+ out = m.ask(args.message)
230
+ elif args.cmd == "agent":
231
+ out = _cmd_agent(args, m)
232
+ else:
233
+ out = _cmd_import(args, m)
234
+ _print(out)
235
+ m.close()
236
+ return 0
237
+ except MembaseError as e:
238
+ print(f"error: {e}", file=sys.stderr)
239
+ return 1
240
+ except ValueError as e:
241
+ print(f"error: {e}", file=sys.stderr)
242
+ return 2
243
+
244
+
245
+ if __name__ == "__main__":
246
+ sys.exit(main())