memorius 0.7.0__tar.gz → 0.7.2__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.
- {memorius-0.7.0/memorius.egg-info → memorius-0.7.2}/PKG-INFO +52 -46
- {memorius-0.7.0 → memorius-0.7.2}/README.md +43 -30
- {memorius-0.7.0 → memorius-0.7.2}/manifest.yaml +1 -1
- {memorius-0.7.0 → memorius-0.7.2}/memorius/backup.py +31 -2
- {memorius-0.7.0 → memorius-0.7.2}/memorius/cli/main.py +128 -17
- {memorius-0.7.0 → memorius-0.7.2}/memorius/cli/obsidian.py +9 -7
- {memorius-0.7.0 → memorius-0.7.2}/memorius/config.py +1 -1
- {memorius-0.7.0 → memorius-0.7.2}/memorius/consolidation.py +0 -1
- {memorius-0.7.0 → memorius-0.7.2}/memorius/context_inject.py +1 -1
- {memorius-0.7.0 → memorius-0.7.2}/memorius/data/examples/quickstart.py +1 -1
- {memorius-0.7.0 → memorius-0.7.2}/memorius/doctor.py +6 -8
- {memorius-0.7.0 → memorius-0.7.2}/memorius/embeddings.py +6 -2
- {memorius-0.7.0 → memorius-0.7.2}/memorius/graph.py +134 -0
- memorius-0.7.2/memorius/graph_visualizer.py +905 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/hooks/cli.py +0 -2
- {memorius-0.7.0 → memorius-0.7.2}/memorius/hooks/engine.py +61 -33
- {memorius-0.7.0 → memorius-0.7.2}/memorius/hooks/models.py +1 -1
- {memorius-0.7.0 → memorius-0.7.2}/memorius/llm_extract.py +36 -26
- {memorius-0.7.0 → memorius-0.7.2}/memorius/mcp_server.py +75 -9
- {memorius-0.7.0 → memorius-0.7.2}/memorius/meta_store.py +54 -8
- {memorius-0.7.0 → memorius-0.7.2}/memorius/model_download.py +15 -3
- {memorius-0.7.0 → memorius-0.7.2}/memorius/normalizers/__init__.py +0 -4
- {memorius-0.7.0 → memorius-0.7.2}/memorius/normalizers/cli.py +1 -4
- {memorius-0.7.0 → memorius-0.7.2}/memorius/obsidian.py +6 -1
- {memorius-0.7.0 → memorius-0.7.2}/memorius/plugin_gen/cli.py +1 -5
- {memorius-0.7.0 → memorius-0.7.2}/memorius/reranker.py +1 -1
- {memorius-0.7.0 → memorius-0.7.2}/memorius/rest_server.py +125 -11
- {memorius-0.7.0 → memorius-0.7.2}/memorius/search_module.py +5 -8
- {memorius-0.7.0 → memorius-0.7.2}/memorius/sqlite_vec_store.py +1 -1
- {memorius-0.7.0 → memorius-0.7.2}/memorius/store_module.py +5 -14
- {memorius-0.7.0 → memorius-0.7.2}/memorius/temporal.py +0 -1
- {memorius-0.7.0 → memorius-0.7.2}/memorius/utils.py +17 -1
- {memorius-0.7.0 → memorius-0.7.2}/memorius/validation.py +36 -1
- {memorius-0.7.0 → memorius-0.7.2}/memorius/vault.py +85 -36
- {memorius-0.7.0 → memorius-0.7.2/memorius.egg-info}/PKG-INFO +52 -46
- {memorius-0.7.0 → memorius-0.7.2}/memorius.egg-info/SOURCES.txt +2 -0
- memorius-0.7.2/memorius.egg-info/requires.txt +14 -0
- {memorius-0.7.0 → memorius-0.7.2}/pyproject.toml +9 -21
- memorius-0.7.2/tests/test_graph_visualizer.py +152 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_validation.py +22 -0
- memorius-0.7.0/memorius.egg-info/requires.txt +0 -26
- {memorius-0.7.0 → memorius-0.7.2}/LICENSE +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/MANIFEST.in +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/examples/quickstart.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/hooks/memorius-hook.sh +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/__init__.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/cli/__init__.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/data/LICENSE +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/data/__init__.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/data/examples/__init__.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/data/manifest.yaml +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/data/memorius-hook.sh +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/factcheck.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/hooks/__init__.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/hooks/adapters/__init__.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/hooks/adapters/base.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/hooks/adapters/claude_code.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/hooks/adapters/codex.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/hooks/adapters/gemini.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/hooks/adapters/generic.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/hooks/adapters/openclaude.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/hooks/adapters/openclaw.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/hooks/adapters/opencode.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/hooks/adapters/pi.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/models.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/plugin_gen/__init__.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/session.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/vector_store.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/vector_store_base.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius/web_search.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius.egg-info/dependency_links.txt +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius.egg-info/entry_points.txt +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/memorius.egg-info/top_level.txt +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/setup.cfg +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_core.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_delete.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_domain_modules.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_encoding_regression.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_feature_backup.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_feature_batch_pagination.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_feature_consolidation_scaling.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_feature_contradiction_edges.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_feature_cors.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_feature_crud.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_feature_daemon.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_feature_doctor.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_feature_factcheck_access.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_feature_graph_retrieval.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_feature_prune.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_feature_rerank.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_feature_search_filtering.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_feature_sqlite_vec.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_feature_ttl.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_features.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_graph.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_integration.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_polish_regressions.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_regression_bugs.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_temporal.py +0 -0
- {memorius-0.7.0 → memorius-0.7.2}/tests/test_web_search.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: memorius
|
|
3
|
-
Version: 0.7.
|
|
4
|
-
Summary: Self-contained memory vault for any AI agent
|
|
3
|
+
Version: 0.7.2
|
|
4
|
+
Summary: Self-contained memory vault for any AI agent — vector search, session diaries, and agent-agnostic hooks.
|
|
5
5
|
Author-email: dimonapatrick243 <dimona.patrick@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/Dream-Pixels-Forge/memorius
|
|
@@ -17,30 +17,23 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
|
17
17
|
Requires-Python: >=3.11
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
License-File: LICENSE
|
|
20
|
-
Requires-Dist: pyyaml>=6.0
|
|
21
|
-
Requires-Dist: chromadb>=0.4.
|
|
22
|
-
Requires-Dist: httpx>=0.
|
|
23
|
-
Requires-Dist: fastapi>=0.104.
|
|
20
|
+
Requires-Dist: pyyaml>=6.0.1
|
|
21
|
+
Requires-Dist: chromadb>=0.4.22
|
|
22
|
+
Requires-Dist: httpx>=0.25.0
|
|
23
|
+
Requires-Dist: fastapi>=0.104.1
|
|
24
24
|
Requires-Dist: uvicorn[standard]>=0.24.0
|
|
25
25
|
Requires-Dist: pydantic>=2.0.0
|
|
26
26
|
Requires-Dist: sse-starlette>=1.0.0
|
|
27
|
-
|
|
28
|
-
Requires-Dist:
|
|
29
|
-
|
|
30
|
-
Requires-Dist: openai>=1.0.0; extra == "openai"
|
|
31
|
-
Provides-Extra: ranker
|
|
32
|
-
Requires-Dist: sentence-transformers>=2.2.0; extra == "ranker"
|
|
33
|
-
Provides-Extra: single-file
|
|
34
|
-
Requires-Dist: sqlite-vec>=0.1.0; extra == "single-file"
|
|
35
|
-
Provides-Extra: all
|
|
36
|
-
Requires-Dist: memorius[local-embeddings,openai,ranker,single-file]; extra == "all"
|
|
27
|
+
Requires-Dist: sentence-transformers>=2.6.0
|
|
28
|
+
Requires-Dist: openai>=1.10.0
|
|
29
|
+
Requires-Dist: sqlite-vec>=0.1.0
|
|
37
30
|
Provides-Extra: dev
|
|
38
31
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
39
32
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
40
33
|
Dynamic: license-file
|
|
41
34
|
|
|
42
35
|
# Memorius
|
|
43
|
-
<img
|
|
36
|
+
<img alt="memorius" src="assets/banner.png" />
|
|
44
37
|
|
|
45
38
|
<p align="center">
|
|
46
39
|
<strong>Universal memory vault for any AI agent.</strong>
|
|
@@ -52,7 +45,7 @@ Dynamic: license-file
|
|
|
52
45
|
<a href="https://github.com/Dream-Pixels-Forge/memorius/actions/workflows/publish.yml"><img src="https://github.com/Dream-Pixels-Forge/memorius/actions/workflows/publish.yml/badge.svg" alt="PyPI publish"></a>
|
|
53
46
|
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license"></a>
|
|
54
47
|
<a href="https://python.org"><img src="https://img.shields.io/badge/python-3.11%20%7C%203.12-blue" alt="Python 3.11 | 3.12"></a>
|
|
55
|
-
<a href="https://github.com/Dream-Pixels-Forge/memorius/blob/main/CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-
|
|
48
|
+
<a href="https://github.com/Dream-Pixels-Forge/memorius/blob/main/CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v0.7.1-green" alt="Changelog"></a>
|
|
56
49
|
</p>
|
|
57
50
|
|
|
58
51
|
> **Works with Claude Code, Codex CLI, Gemini CLI, OpenClaude, OpenCode, Pi, and any MCP-compatible agent.**
|
|
@@ -117,23 +110,33 @@ memorius diary "session-001" --title "Research findings"
|
|
|
117
110
|
|
|
118
111
|
<img width="979" height="632" alt="Screenshot from 2026-06-18 15-14-39" src="https://github.com/user-attachments/assets/6cbca59b-1a3c-4b1d-85e8-dd5b256b9061" />
|
|
119
112
|
|
|
120
|
-
## What's New in
|
|
113
|
+
## What's New in v0.7.2
|
|
121
114
|
|
|
122
|
-
###
|
|
123
|
-
- **
|
|
124
|
-
- **
|
|
125
|
-
- **
|
|
126
|
-
- **
|
|
115
|
+
### Security Hardening (Comprehensive Audit)
|
|
116
|
+
- **Path traversal guards** — `export_graph_html` and CLI obsidian export validate paths stay within storage/export directories.
|
|
117
|
+
- **HTML injection prevention** — Graph visualizer title escaped via `html.escape()`.
|
|
118
|
+
- **Command injection prevention** — Windows daemon startup passes host/port/tls via environment variables.
|
|
119
|
+
- **LLM extraction trust model** — System/user message split isolates extraction rules from user-provided content.
|
|
120
|
+
- **Template injection prevention** — Recursion depth limited to 10 in `_substitute_templates`.
|
|
121
|
+
- **UUID validation on MCP handlers** — `memorius_contradictions`, `memorius_get`, `memorius_update`, `memorius_delete` validate memory_id format.
|
|
122
|
+
- **Dependency version pinning** — Minimum versions set for `chromadb>=0.4.22`, `sentence-transformers>=2.6.0`, `httpx>=0.25.0`, `openai>=1.1.0`.
|
|
123
|
+
- **Rate limiter memory growth fix** — Sweep every 200 requests + IP count cap (10,000).
|
|
124
|
+
- **Security documentation** — MCP tool descriptions include security notes about treating memory content as untrusted data.
|
|
127
125
|
|
|
128
|
-
###
|
|
129
|
-
- **
|
|
130
|
-
- **
|
|
131
|
-
- **Dead code removal** — HNSW switchover path removed from consolidation.
|
|
132
|
-
- **26 new tests** — `SearchModule` (7) and `StoreModule` (19) unit tests.
|
|
126
|
+
### Added
|
|
127
|
+
- **Knowledge Graph Visualizer** — standalone interactive HTML visualization with force-directed canvas layout, shelf/relation filtering, search, node inspector, and zoom/pan. Access via `memorius graph view`, MCP `memorius_graph_export`, or REST `GET /graph/view`.
|
|
128
|
+
- **Dependency consolidation** — `sentence-transformers`, `openai`, `sqlite-vec` now included by default. `pip install memorius` includes all features.
|
|
133
129
|
|
|
134
|
-
###
|
|
135
|
-
- **
|
|
136
|
-
- **
|
|
130
|
+
### Code Quality
|
|
131
|
+
- **Extracted lazy-init pattern** — `_get_store_module()` and `_get_search_module()` helpers eliminate repeated init blocks.
|
|
132
|
+
- **Deduplicated JSON parsing** — `safe_parse_json()` in `utils.py` used across `store_module.py` and `search_module.py`.
|
|
133
|
+
- **Missing exception annotations** — 6 unannotated blocks in `doctor.py` and `context_inject.py` now documented.
|
|
134
|
+
- **Sealed message-chain breach** — MCP server calls `engine.list_diaries()` instead of `_engine._meta.list_diaries()`.
|
|
135
|
+
- **Clarified HookEngine naming** — `_get_engine()` → `_get_vault()` to avoid confusion with the HookEngine class.
|
|
136
|
+
- **SQL PRAGMA whitelist** — `_get_columns()` validates table names against `frozenset` allowlist.
|
|
137
|
+
- **UUID validation on batch queries** — `get_memory_meta_batch()` validates IDs before `WHERE IN` interpolation.
|
|
138
|
+
- **Typed module dependencies** — `SearchModule`/`StoreModule` `meta` param typed as `SQLiteStore` (was `Any`).
|
|
139
|
+
- **Scoped connection cleanup** — `VaultEngine.close()` closes only its own DB, not all thread connections.
|
|
137
140
|
|
|
138
141
|
See [CHANGELOG.md](CHANGELOG.md) for full release history.
|
|
139
142
|
|
|
@@ -237,8 +240,8 @@ Memorius uses a **VectorStore ABC** (abstract base class) for swappable backends
|
|
|
237
240
|
|
|
238
241
|
| Backend | Dependency | Description |
|
|
239
242
|
|---|---|---|
|
|
240
|
-
| **ChromaDB** (default) | ChromaDB (
|
|
241
|
-
| **SQLite-vec** |
|
|
243
|
+
| **ChromaDB** (default) | ChromaDB (included) | Persistent HNSW cosine search, auto-migration from legacy collections |
|
|
244
|
+
| **SQLite-vec** | SQLite-vec (included) | Single-file alternative, no ChromaDB dependency |
|
|
242
245
|
|
|
243
246
|
Set `storage.type: sqlite-vec` in config or `MEMORIUS_STORAGE_TYPE=sqlite-vec` to use the lightweight backend.
|
|
244
247
|
|
|
@@ -246,8 +249,8 @@ Set `storage.type: sqlite-vec` in config or `MEMORIUS_STORAGE_TYPE=sqlite-vec` t
|
|
|
246
249
|
|
|
247
250
|
| Provider | Requirement | Quality |
|
|
248
251
|
|---|---|---|
|
|
249
|
-
| `chroma-default` | ChromaDB (
|
|
250
|
-
| `sentence-transformers` | `
|
|
252
|
+
| `chroma-default` | ChromaDB (included ONNX) | Good (384d) |
|
|
253
|
+
| `sentence-transformers` | `sentence-transformers` (included) | Better (768d+) |
|
|
251
254
|
| `openai` | `OPENAI_API_KEY` env var | Best (1536d or 3072d) |
|
|
252
255
|
|
|
253
256
|
Providers are extensible via `EmbeddingFactory.register()` for custom runtime providers.
|
|
@@ -267,7 +270,8 @@ Memorius implements the **Ebbinghaus forgetting curve** to rank memories by rele
|
|
|
267
270
|
- **Auto-linking** on every `store()` call by content proximity (Jaccard word overlap or vector cosine)
|
|
268
271
|
- **BFS expansion** from search results (`--expand-graph` on CLI / `expand_graph=True` on MCP)
|
|
269
272
|
- **Contradiction edges** persisted by `factcheck` (bidirectional `relation='contradicts'`)
|
|
270
|
-
- **Graph
|
|
273
|
+
- **Graph visualization** via `memorius graph view` (interactive browser UI) or `memorius graph export-html`
|
|
274
|
+
- **Graph statistics** via `memorius graph stats` or MCP `memorius_graph_stats`
|
|
271
275
|
|
|
272
276
|
## Cross-Encoder Reranker
|
|
273
277
|
|
|
@@ -279,7 +283,7 @@ memorius search "query" --rerank
|
|
|
279
283
|
|
|
280
284
|
| Provider | Requirement |
|
|
281
285
|
|---|---|
|
|
282
|
-
| `cross-encoder/ms-marco-MiniLM-L-6-v2` | `pip install memorius
|
|
286
|
+
| `cross-encoder/ms-marco-MiniLM-L-6-v2` | Included out of the box (`pip install memorius`) |
|
|
283
287
|
|
|
284
288
|
Adds `__rerank_score__` to memory metadata for transparent ranking.
|
|
285
289
|
|
|
@@ -518,7 +522,7 @@ any MCP-compatible client by pointing it at the MCP server:
|
|
|
518
522
|
}
|
|
519
523
|
```
|
|
520
524
|
|
|
521
|
-
Available MCP tools (
|
|
525
|
+
Available MCP tools (21 tools):
|
|
522
526
|
|
|
523
527
|
| Tool | Description |
|
|
524
528
|
|---|---|
|
|
@@ -799,15 +803,17 @@ All inputs are validated for security and correctness:
|
|
|
799
803
|
- CORS: restrictive by default (Obsidian app + localhost only)
|
|
800
804
|
- Path traversal detection on Obsidian export
|
|
801
805
|
|
|
802
|
-
##
|
|
806
|
+
## Installation
|
|
807
|
+
|
|
808
|
+
```bash
|
|
809
|
+
pip install memorius
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
All features — including the cross-encoder reranker, sentence-transformers, SQLite-vec single-file backend, OpenAI embeddings support, REST API server, and graph visualizer — are included out of the box.
|
|
803
813
|
|
|
814
|
+
For development:
|
|
804
815
|
```bash
|
|
805
|
-
pip install memorius[
|
|
806
|
-
pip install memorius[openai] # openai SDK
|
|
807
|
-
pip install memorius[ranker] # cross-encoder reranker
|
|
808
|
-
pip install memorius[single-file] # sqlite-vec backend
|
|
809
|
-
pip install memorius[all] # everything above
|
|
810
|
-
pip install memorius[dev] # pytest + pytest-asyncio
|
|
816
|
+
pip install memorius[dev] # pytest + pytest-asyncio
|
|
811
817
|
```
|
|
812
818
|
|
|
813
819
|
## Installed CLI Entry Points
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# Memorius
|
|
2
|
-
<img
|
|
2
|
+
<img alt="memorius" src="assets/banner.png" />
|
|
3
3
|
|
|
4
4
|
<p align="center">
|
|
5
5
|
<strong>Universal memory vault for any AI agent.</strong>
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<a href="https://github.com/Dream-Pixels-Forge/memorius/actions/workflows/publish.yml"><img src="https://github.com/Dream-Pixels-Forge/memorius/actions/workflows/publish.yml/badge.svg" alt="PyPI publish"></a>
|
|
12
12
|
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license"></a>
|
|
13
13
|
<a href="https://python.org"><img src="https://img.shields.io/badge/python-3.11%20%7C%203.12-blue" alt="Python 3.11 | 3.12"></a>
|
|
14
|
-
<a href="https://github.com/Dream-Pixels-Forge/memorius/blob/main/CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-
|
|
14
|
+
<a href="https://github.com/Dream-Pixels-Forge/memorius/blob/main/CHANGELOG.md"><img src="https://img.shields.io/badge/changelog-v0.7.1-green" alt="Changelog"></a>
|
|
15
15
|
</p>
|
|
16
16
|
|
|
17
17
|
> **Works with Claude Code, Codex CLI, Gemini CLI, OpenClaude, OpenCode, Pi, and any MCP-compatible agent.**
|
|
@@ -76,23 +76,33 @@ memorius diary "session-001" --title "Research findings"
|
|
|
76
76
|
|
|
77
77
|
<img width="979" height="632" alt="Screenshot from 2026-06-18 15-14-39" src="https://github.com/user-attachments/assets/6cbca59b-1a3c-4b1d-85e8-dd5b256b9061" />
|
|
78
78
|
|
|
79
|
-
## What's New in
|
|
79
|
+
## What's New in v0.7.2
|
|
80
80
|
|
|
81
|
-
###
|
|
82
|
-
- **
|
|
83
|
-
- **
|
|
84
|
-
- **
|
|
85
|
-
- **
|
|
81
|
+
### Security Hardening (Comprehensive Audit)
|
|
82
|
+
- **Path traversal guards** — `export_graph_html` and CLI obsidian export validate paths stay within storage/export directories.
|
|
83
|
+
- **HTML injection prevention** — Graph visualizer title escaped via `html.escape()`.
|
|
84
|
+
- **Command injection prevention** — Windows daemon startup passes host/port/tls via environment variables.
|
|
85
|
+
- **LLM extraction trust model** — System/user message split isolates extraction rules from user-provided content.
|
|
86
|
+
- **Template injection prevention** — Recursion depth limited to 10 in `_substitute_templates`.
|
|
87
|
+
- **UUID validation on MCP handlers** — `memorius_contradictions`, `memorius_get`, `memorius_update`, `memorius_delete` validate memory_id format.
|
|
88
|
+
- **Dependency version pinning** — Minimum versions set for `chromadb>=0.4.22`, `sentence-transformers>=2.6.0`, `httpx>=0.25.0`, `openai>=1.1.0`.
|
|
89
|
+
- **Rate limiter memory growth fix** — Sweep every 200 requests + IP count cap (10,000).
|
|
90
|
+
- **Security documentation** — MCP tool descriptions include security notes about treating memory content as untrusted data.
|
|
86
91
|
|
|
87
|
-
###
|
|
88
|
-
- **
|
|
89
|
-
- **
|
|
90
|
-
- **Dead code removal** — HNSW switchover path removed from consolidation.
|
|
91
|
-
- **26 new tests** — `SearchModule` (7) and `StoreModule` (19) unit tests.
|
|
92
|
+
### Added
|
|
93
|
+
- **Knowledge Graph Visualizer** — standalone interactive HTML visualization with force-directed canvas layout, shelf/relation filtering, search, node inspector, and zoom/pan. Access via `memorius graph view`, MCP `memorius_graph_export`, or REST `GET /graph/view`.
|
|
94
|
+
- **Dependency consolidation** — `sentence-transformers`, `openai`, `sqlite-vec` now included by default. `pip install memorius` includes all features.
|
|
92
95
|
|
|
93
|
-
###
|
|
94
|
-
- **
|
|
95
|
-
- **
|
|
96
|
+
### Code Quality
|
|
97
|
+
- **Extracted lazy-init pattern** — `_get_store_module()` and `_get_search_module()` helpers eliminate repeated init blocks.
|
|
98
|
+
- **Deduplicated JSON parsing** — `safe_parse_json()` in `utils.py` used across `store_module.py` and `search_module.py`.
|
|
99
|
+
- **Missing exception annotations** — 6 unannotated blocks in `doctor.py` and `context_inject.py` now documented.
|
|
100
|
+
- **Sealed message-chain breach** — MCP server calls `engine.list_diaries()` instead of `_engine._meta.list_diaries()`.
|
|
101
|
+
- **Clarified HookEngine naming** — `_get_engine()` → `_get_vault()` to avoid confusion with the HookEngine class.
|
|
102
|
+
- **SQL PRAGMA whitelist** — `_get_columns()` validates table names against `frozenset` allowlist.
|
|
103
|
+
- **UUID validation on batch queries** — `get_memory_meta_batch()` validates IDs before `WHERE IN` interpolation.
|
|
104
|
+
- **Typed module dependencies** — `SearchModule`/`StoreModule` `meta` param typed as `SQLiteStore` (was `Any`).
|
|
105
|
+
- **Scoped connection cleanup** — `VaultEngine.close()` closes only its own DB, not all thread connections.
|
|
96
106
|
|
|
97
107
|
See [CHANGELOG.md](CHANGELOG.md) for full release history.
|
|
98
108
|
|
|
@@ -196,8 +206,8 @@ Memorius uses a **VectorStore ABC** (abstract base class) for swappable backends
|
|
|
196
206
|
|
|
197
207
|
| Backend | Dependency | Description |
|
|
198
208
|
|---|---|---|
|
|
199
|
-
| **ChromaDB** (default) | ChromaDB (
|
|
200
|
-
| **SQLite-vec** |
|
|
209
|
+
| **ChromaDB** (default) | ChromaDB (included) | Persistent HNSW cosine search, auto-migration from legacy collections |
|
|
210
|
+
| **SQLite-vec** | SQLite-vec (included) | Single-file alternative, no ChromaDB dependency |
|
|
201
211
|
|
|
202
212
|
Set `storage.type: sqlite-vec` in config or `MEMORIUS_STORAGE_TYPE=sqlite-vec` to use the lightweight backend.
|
|
203
213
|
|
|
@@ -205,8 +215,8 @@ Set `storage.type: sqlite-vec` in config or `MEMORIUS_STORAGE_TYPE=sqlite-vec` t
|
|
|
205
215
|
|
|
206
216
|
| Provider | Requirement | Quality |
|
|
207
217
|
|---|---|---|
|
|
208
|
-
| `chroma-default` | ChromaDB (
|
|
209
|
-
| `sentence-transformers` | `
|
|
218
|
+
| `chroma-default` | ChromaDB (included ONNX) | Good (384d) |
|
|
219
|
+
| `sentence-transformers` | `sentence-transformers` (included) | Better (768d+) |
|
|
210
220
|
| `openai` | `OPENAI_API_KEY` env var | Best (1536d or 3072d) |
|
|
211
221
|
|
|
212
222
|
Providers are extensible via `EmbeddingFactory.register()` for custom runtime providers.
|
|
@@ -226,7 +236,8 @@ Memorius implements the **Ebbinghaus forgetting curve** to rank memories by rele
|
|
|
226
236
|
- **Auto-linking** on every `store()` call by content proximity (Jaccard word overlap or vector cosine)
|
|
227
237
|
- **BFS expansion** from search results (`--expand-graph` on CLI / `expand_graph=True` on MCP)
|
|
228
238
|
- **Contradiction edges** persisted by `factcheck` (bidirectional `relation='contradicts'`)
|
|
229
|
-
- **Graph
|
|
239
|
+
- **Graph visualization** via `memorius graph view` (interactive browser UI) or `memorius graph export-html`
|
|
240
|
+
- **Graph statistics** via `memorius graph stats` or MCP `memorius_graph_stats`
|
|
230
241
|
|
|
231
242
|
## Cross-Encoder Reranker
|
|
232
243
|
|
|
@@ -238,7 +249,7 @@ memorius search "query" --rerank
|
|
|
238
249
|
|
|
239
250
|
| Provider | Requirement |
|
|
240
251
|
|---|---|
|
|
241
|
-
| `cross-encoder/ms-marco-MiniLM-L-6-v2` | `pip install memorius
|
|
252
|
+
| `cross-encoder/ms-marco-MiniLM-L-6-v2` | Included out of the box (`pip install memorius`) |
|
|
242
253
|
|
|
243
254
|
Adds `__rerank_score__` to memory metadata for transparent ranking.
|
|
244
255
|
|
|
@@ -477,7 +488,7 @@ any MCP-compatible client by pointing it at the MCP server:
|
|
|
477
488
|
}
|
|
478
489
|
```
|
|
479
490
|
|
|
480
|
-
Available MCP tools (
|
|
491
|
+
Available MCP tools (21 tools):
|
|
481
492
|
|
|
482
493
|
| Tool | Description |
|
|
483
494
|
|---|---|
|
|
@@ -758,15 +769,17 @@ All inputs are validated for security and correctness:
|
|
|
758
769
|
- CORS: restrictive by default (Obsidian app + localhost only)
|
|
759
770
|
- Path traversal detection on Obsidian export
|
|
760
771
|
|
|
761
|
-
##
|
|
772
|
+
## Installation
|
|
773
|
+
|
|
774
|
+
```bash
|
|
775
|
+
pip install memorius
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
All features — including the cross-encoder reranker, sentence-transformers, SQLite-vec single-file backend, OpenAI embeddings support, REST API server, and graph visualizer — are included out of the box.
|
|
762
779
|
|
|
780
|
+
For development:
|
|
763
781
|
```bash
|
|
764
|
-
pip install memorius[
|
|
765
|
-
pip install memorius[openai] # openai SDK
|
|
766
|
-
pip install memorius[ranker] # cross-encoder reranker
|
|
767
|
-
pip install memorius[single-file] # sqlite-vec backend
|
|
768
|
-
pip install memorius[all] # everything above
|
|
769
|
-
pip install memorius[dev] # pytest + pytest-asyncio
|
|
782
|
+
pip install memorius[dev] # pytest + pytest-asyncio
|
|
770
783
|
```
|
|
771
784
|
|
|
772
785
|
## Installed CLI Entry Points
|
|
@@ -13,17 +13,29 @@ from __future__ import annotations
|
|
|
13
13
|
|
|
14
14
|
import json
|
|
15
15
|
import logging
|
|
16
|
-
import os
|
|
17
|
-
import re
|
|
18
16
|
from datetime import datetime, timezone
|
|
19
17
|
from pathlib import Path
|
|
20
18
|
from typing import Any
|
|
21
19
|
|
|
20
|
+
from memorius.validation import validate_name as _validate_name
|
|
21
|
+
|
|
22
22
|
logger = logging.getLogger("memorius.backup")
|
|
23
23
|
|
|
24
24
|
SCHEMA_VERSION = 1
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
def _validate_hierarchy_names(vaults, shelves, folders, notes):
|
|
28
|
+
"""Validate all hierarchy items before import to prevent path traversal."""
|
|
29
|
+
for v in vaults:
|
|
30
|
+
_validate_name(v.get("name", ""), "vault")
|
|
31
|
+
for s in shelves:
|
|
32
|
+
_validate_name(s.get("name", ""), "shelf")
|
|
33
|
+
for f in folders:
|
|
34
|
+
_validate_name(f.get("name", ""), "folder")
|
|
35
|
+
for n in notes:
|
|
36
|
+
_validate_name(n.get("name", ""), "note")
|
|
37
|
+
|
|
38
|
+
|
|
27
39
|
# ── JSON export / import ─────────────────────────────────────────────────────
|
|
28
40
|
|
|
29
41
|
def export_json(engine, dest: str | Path) -> Path:
|
|
@@ -99,6 +111,13 @@ def import_json(engine, src: str | Path, *, merge: bool = True) -> dict[str, Any
|
|
|
99
111
|
}
|
|
100
112
|
|
|
101
113
|
# ── hierarchy ──
|
|
114
|
+
# Validate all imported names to prevent path traversal
|
|
115
|
+
_validate_hierarchy_names(
|
|
116
|
+
payload.get("vaults", []),
|
|
117
|
+
payload.get("shelves", []),
|
|
118
|
+
payload.get("folders", []),
|
|
119
|
+
payload.get("notes", []),
|
|
120
|
+
)
|
|
102
121
|
engine._meta.import_hierarchy(
|
|
103
122
|
payload.get("vaults", []),
|
|
104
123
|
payload.get("shelves", []),
|
|
@@ -112,6 +131,11 @@ def import_json(engine, src: str | Path, *, merge: bool = True) -> dict[str, Any
|
|
|
112
131
|
|
|
113
132
|
# ── memories ──
|
|
114
133
|
for m in payload.get("memories", []):
|
|
134
|
+
# Validate memory hierarchy fields to prevent path traversal
|
|
135
|
+
_validate_name(m["vault"], "vault")
|
|
136
|
+
_validate_name(m["shelf"], "shelf")
|
|
137
|
+
_validate_name(m["folder"], "folder")
|
|
138
|
+
_validate_name(m["note"], "note")
|
|
115
139
|
engine._meta.ensure_note(m["vault"], m["shelf"], m["folder"], m["note"])
|
|
116
140
|
imported = engine._meta.import_memory_meta(m, merge=merge)
|
|
117
141
|
if imported:
|
|
@@ -174,6 +198,11 @@ def export_markdown(engine, dest: str | Path) -> Path:
|
|
|
174
198
|
edges = edges_by_source.get(mid, [])
|
|
175
199
|
|
|
176
200
|
md_path = dest / m["vault"] / m["shelf"] / m["folder"] / m["note"] / f"{mid}.md"
|
|
201
|
+
# Path traversal guard: resolved path must stay inside dest
|
|
202
|
+
md_path_resolved = md_path.resolve()
|
|
203
|
+
if not str(md_path_resolved).startswith(str(dest.resolve())):
|
|
204
|
+
logger.warning("Skipping memory %s: path traversal detected", mid)
|
|
205
|
+
continue
|
|
177
206
|
md_path.parent.mkdir(parents=True, exist_ok=True)
|
|
178
207
|
|
|
179
208
|
# Build YAML frontmatter
|
|
@@ -20,6 +20,8 @@ Usage:
|
|
|
20
20
|
memorius obsidian list List notes in an Obsidian vault
|
|
21
21
|
memorius obsidian import Import Obsidian notes as memories
|
|
22
22
|
memorius obsidian export Export memories as Obsidian notes
|
|
23
|
+
memorius graph view Open interactive knowledge graph in browser
|
|
24
|
+
memorius graph export-html Export standalone interactive HTML graph
|
|
23
25
|
memorius web <query> Search the internet (web fallback)
|
|
24
26
|
"""
|
|
25
27
|
|
|
@@ -119,10 +121,12 @@ def main():
|
|
|
119
121
|
|
|
120
122
|
serve_rest_p = subparsers.add_parser("serve-rest", help="Start REST API server")
|
|
121
123
|
serve_rest_p.add_argument("--port", type=int, default=None, help="Port")
|
|
122
|
-
serve_rest_p.add_argument("--host", default=None, help="Host")
|
|
124
|
+
serve_rest_p.add_argument("--host", default=None, help="Host (default: 127.0.0.1)")
|
|
123
125
|
serve_rest_p.add_argument("--daemon", action="store_true", help="Run as background daemon")
|
|
124
126
|
serve_rest_p.add_argument("--stop", action="store_true", help="Stop running daemon")
|
|
125
127
|
serve_rest_p.add_argument("--pid-file", default=None, help="PID file path")
|
|
128
|
+
serve_rest_p.add_argument("--tls-cert", default=None, help="Path to TLS certificate file (PEM)")
|
|
129
|
+
serve_rest_p.add_argument("--tls-key", default=None, help="Path to TLS private key file (PEM)")
|
|
126
130
|
|
|
127
131
|
config_p = subparsers.add_parser("config", help="Show configuration")
|
|
128
132
|
config_p.add_argument("--show", action="store_true", default=True, help="Show config")
|
|
@@ -201,6 +205,35 @@ def main():
|
|
|
201
205
|
list_p.add_argument("--limit", type=int, default=10, help="Max results per page (default: 10)")
|
|
202
206
|
list_p.add_argument("--cursor", help="Cursor for next page (timestamp)")
|
|
203
207
|
|
|
208
|
+
# ── Graph subcommands ──
|
|
209
|
+
graph_p = subparsers.add_parser("graph", help="Visualize and inspect the knowledge graph")
|
|
210
|
+
graph_sub = graph_p.add_subparsers(dest="subcommand")
|
|
211
|
+
|
|
212
|
+
g_view_p = graph_sub.add_parser("view", help="Open interactive knowledge graph visualizer in browser")
|
|
213
|
+
g_view_p.add_argument("--vault", default=None, help="Filter by vault")
|
|
214
|
+
g_view_p.add_argument("--shelf", default=None, help="Filter by shelf")
|
|
215
|
+
g_view_p.add_argument("--relation", default=None, help="Filter by relation type (related, references, contradicts, co_occurs)")
|
|
216
|
+
g_view_p.add_argument("--min-weight", type=float, default=0.0, help="Minimum edge weight (default: 0.0)")
|
|
217
|
+
g_view_p.add_argument("--limit", type=int, default=500, help="Max nodes to display (default: 500)")
|
|
218
|
+
g_view_p.add_argument("--no-open", action="store_true", help="Don't open browser automatically")
|
|
219
|
+
|
|
220
|
+
g_export_p = graph_sub.add_parser("export-html", help="Export standalone interactive HTML visualization")
|
|
221
|
+
g_export_p.add_argument("--output", "-o", default="graph.html", help="Output file path (default: graph.html)")
|
|
222
|
+
g_export_p.add_argument("--vault", default=None, help="Filter by vault")
|
|
223
|
+
g_export_p.add_argument("--shelf", default=None, help="Filter by shelf")
|
|
224
|
+
g_export_p.add_argument("--relation", default=None, help="Filter by relation type")
|
|
225
|
+
g_export_p.add_argument("--min-weight", type=float, default=0.0, help="Minimum edge weight")
|
|
226
|
+
g_export_p.add_argument("--limit", type=int, default=500, help="Max nodes to export")
|
|
227
|
+
|
|
228
|
+
graph_sub.add_parser("stats", help="Display knowledge graph statistics")
|
|
229
|
+
|
|
230
|
+
g_json_p = graph_sub.add_parser("json", help="Output graph topology as JSON")
|
|
231
|
+
g_json_p.add_argument("--vault", default=None, help="Filter by vault")
|
|
232
|
+
g_json_p.add_argument("--shelf", default=None, help="Filter by shelf")
|
|
233
|
+
g_json_p.add_argument("--relation", default=None, help="Filter by relation type")
|
|
234
|
+
g_json_p.add_argument("--min-weight", type=float, default=0.0, help="Minimum edge weight")
|
|
235
|
+
g_json_p.add_argument("--limit", type=int, default=500, help="Max nodes")
|
|
236
|
+
|
|
204
237
|
# ── Obsidian subcommands ──
|
|
205
238
|
obsidian_p = subparsers.add_parser("obsidian", help="Interact with Obsidian vaults")
|
|
206
239
|
obsidian_sub = obsidian_p.add_subparsers(dest="subcommand")
|
|
@@ -268,6 +301,7 @@ def main():
|
|
|
268
301
|
"import": cmd_import,
|
|
269
302
|
"doctor": cmd_doctor,
|
|
270
303
|
"list": cmd_list,
|
|
304
|
+
"graph": cmd_graph,
|
|
271
305
|
}
|
|
272
306
|
handler = commands.get(args.command)
|
|
273
307
|
if handler:
|
|
@@ -525,10 +559,18 @@ def cmd_serve_rest(engine, args, config):
|
|
|
525
559
|
return
|
|
526
560
|
|
|
527
561
|
if getattr(args, "daemon", False):
|
|
528
|
-
_start_daemon(
|
|
562
|
+
_start_daemon(
|
|
563
|
+
engine, host, port, pid_file,
|
|
564
|
+
tls_cert=getattr(args, "tls_cert", None),
|
|
565
|
+
tls_key=getattr(args, "tls_key", None),
|
|
566
|
+
)
|
|
529
567
|
else:
|
|
530
568
|
from memorius.rest_server import run_rest_server
|
|
531
|
-
run_rest_server(
|
|
569
|
+
run_rest_server(
|
|
570
|
+
engine, host=host, port=port,
|
|
571
|
+
tls_cert=getattr(args, "tls_cert", None),
|
|
572
|
+
tls_key=getattr(args, "tls_key", None),
|
|
573
|
+
)
|
|
532
574
|
|
|
533
575
|
|
|
534
576
|
def _stop_daemon(pid_file: Path):
|
|
@@ -582,27 +624,44 @@ def _stop_daemon(pid_file: Path):
|
|
|
582
624
|
print("Daemon stopped")
|
|
583
625
|
|
|
584
626
|
|
|
585
|
-
def _start_daemon(engine, host: str, port: int, pid_file: Path
|
|
627
|
+
def _start_daemon(engine, host: str, port: int, pid_file: Path,
|
|
628
|
+
tls_cert: str | None = None, tls_key: str | None = None):
|
|
586
629
|
"""Start REST server as a background daemon."""
|
|
587
630
|
import os
|
|
588
631
|
import sys
|
|
589
632
|
import subprocess
|
|
590
|
-
import time
|
|
591
633
|
|
|
592
634
|
if os.name == "nt":
|
|
593
635
|
# Windows: launch detached subprocess
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
636
|
+
# Pass configuration via environment variables to avoid shell injection
|
|
637
|
+
# through string interpolation in python -c commands.
|
|
638
|
+
daemon_script = (
|
|
639
|
+
"import os, sys; sys.stdin = open(os.devnull); "
|
|
640
|
+
"from pathlib import Path; "
|
|
641
|
+
"Path(os.environ['MEMORIUS_PID_FILE']).write_text(str(os.getpid())); "
|
|
642
|
+
"from memorius.rest_server import run_rest_server; "
|
|
643
|
+
"from memorius.vault import VaultEngine; "
|
|
644
|
+
"from memorius.config import load_config; "
|
|
645
|
+
"e = VaultEngine(load_config()); "
|
|
646
|
+
"run_rest_server(e, host=os.environ['MEMORIUS_HOST'], "
|
|
647
|
+
"port=int(os.environ['MEMORIUS_PORT']))"
|
|
648
|
+
)
|
|
649
|
+
env = os.environ.copy()
|
|
650
|
+
env["MEMORIUS_HOST"] = host
|
|
651
|
+
env["MEMORIUS_PORT"] = str(port)
|
|
652
|
+
env["MEMORIUS_PID_FILE"] = str(pid_file)
|
|
653
|
+
if tls_cert and tls_key:
|
|
654
|
+
env["MEMORIUS_TLS_CERT"] = tls_cert
|
|
655
|
+
env["MEMORIUS_TLS_KEY"] = tls_key
|
|
656
|
+
daemon_script = daemon_script.replace(
|
|
657
|
+
"run_rest_server(e, host=os.environ['MEMORIUS_HOST'], "
|
|
658
|
+
"port=int(os.environ['MEMORIUS_PORT']))",
|
|
659
|
+
"run_rest_server(e, host=os.environ['MEMORIUS_HOST'], "
|
|
660
|
+
"port=int(os.environ['MEMORIUS_PORT']), "
|
|
661
|
+
"tls_cert=os.environ.get('MEMORIUS_TLS_CERT'), "
|
|
662
|
+
"tls_key=os.environ.get('MEMORIUS_TLS_KEY'))",
|
|
663
|
+
)
|
|
664
|
+
cmd = [sys.executable, "-c", daemon_script]
|
|
606
665
|
proc = subprocess.Popen(
|
|
607
666
|
cmd,
|
|
608
667
|
creationflags=subprocess.CREATE_NO_WINDOW | subprocess.DETACHED_PROCESS,
|
|
@@ -848,6 +907,58 @@ def cmd_stats(engine, args, config):
|
|
|
848
907
|
print(f" {rel}: {count}")
|
|
849
908
|
|
|
850
909
|
|
|
910
|
+
def cmd_graph(engine, args, config):
|
|
911
|
+
"""Inspect or visualize knowledge graph."""
|
|
912
|
+
sub = getattr(args, "subcommand", None)
|
|
913
|
+
if sub == "stats" or sub is None:
|
|
914
|
+
graph_stats = engine.get_graph_stats()
|
|
915
|
+
print(" Knowledge Graph Statistics:")
|
|
916
|
+
print(f" Nodes: {graph_stats.get('unique_nodes', 0)}")
|
|
917
|
+
print(f" Edges: {graph_stats.get('total_edges', 0)}")
|
|
918
|
+
relations = graph_stats.get('relations', {})
|
|
919
|
+
if relations:
|
|
920
|
+
print(" Relations:")
|
|
921
|
+
for rel, count in relations.items():
|
|
922
|
+
print(f" {rel}: {count}")
|
|
923
|
+
elif sub == "export-html":
|
|
924
|
+
out_file = args.output
|
|
925
|
+
engine.export_graph_html(
|
|
926
|
+
dest=out_file,
|
|
927
|
+
vault=args.vault,
|
|
928
|
+
shelf=args.shelf,
|
|
929
|
+
relation=args.relation,
|
|
930
|
+
min_weight=args.min_weight,
|
|
931
|
+
limit=args.limit,
|
|
932
|
+
)
|
|
933
|
+
print(f"Knowledge graph visualization saved to: {Path(out_file).resolve()}")
|
|
934
|
+
elif sub == "view":
|
|
935
|
+
import tempfile
|
|
936
|
+
import webbrowser
|
|
937
|
+
temp_dir = Path(tempfile.gettempdir())
|
|
938
|
+
html_file = temp_dir / "memorius_graph.html"
|
|
939
|
+
engine.export_graph_html(
|
|
940
|
+
dest=str(html_file),
|
|
941
|
+
vault=args.vault,
|
|
942
|
+
shelf=args.shelf,
|
|
943
|
+
relation=args.relation,
|
|
944
|
+
min_weight=args.min_weight,
|
|
945
|
+
limit=args.limit,
|
|
946
|
+
)
|
|
947
|
+
print(f"Generated visualization: {html_file}")
|
|
948
|
+
if not getattr(args, "no_open", False):
|
|
949
|
+
print("Opening in default browser...")
|
|
950
|
+
webbrowser.open(f"file://{html_file.resolve()}")
|
|
951
|
+
elif sub == "json":
|
|
952
|
+
data = engine.get_graph_data(
|
|
953
|
+
vault=args.vault,
|
|
954
|
+
shelf=args.shelf,
|
|
955
|
+
relation=args.relation,
|
|
956
|
+
min_weight=args.min_weight,
|
|
957
|
+
limit=args.limit,
|
|
958
|
+
)
|
|
959
|
+
print(json.dumps(data, indent=2))
|
|
960
|
+
|
|
961
|
+
|
|
851
962
|
def cmd_get(engine, args, config):
|
|
852
963
|
"""Get a single memory by ID."""
|
|
853
964
|
from memorius.validation import validate_memory_id as _vid
|
|
@@ -11,9 +11,7 @@ Usage:
|
|
|
11
11
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
|
-
import json
|
|
15
14
|
import logging
|
|
16
|
-
import os
|
|
17
15
|
import re
|
|
18
16
|
from pathlib import Path
|
|
19
17
|
from typing import Any
|
|
@@ -21,10 +19,7 @@ from typing import Any
|
|
|
21
19
|
from memorius.vault import VaultEngine
|
|
22
20
|
from memorius.obsidian import (
|
|
23
21
|
resolve_vault_path as _resolve_vault_path,
|
|
24
|
-
scan_vault as _scan_vault,
|
|
25
|
-
parse_note as _parse_note,
|
|
26
22
|
parse_frontmatter as _parse_frontmatter,
|
|
27
|
-
FRONTMATTER_RE,
|
|
28
23
|
)
|
|
29
24
|
|
|
30
25
|
logger = logging.getLogger("memorius.obsidian")
|
|
@@ -158,7 +153,7 @@ def import_notes(
|
|
|
158
153
|
metadata["obsidian_path"] = str(relative)
|
|
159
154
|
metadata["obsidian_title"] = title
|
|
160
155
|
|
|
161
|
-
|
|
156
|
+
engine.store(
|
|
162
157
|
content=store_content,
|
|
163
158
|
vault=target_vault,
|
|
164
159
|
shelf=target_shelf,
|
|
@@ -227,7 +222,14 @@ def export_memories(
|
|
|
227
222
|
continue
|
|
228
223
|
|
|
229
224
|
# Build output path mirroring hierarchy
|
|
230
|
-
note_subdir = export_dir / sh["name"] / f["name"]
|
|
225
|
+
note_subdir = (export_dir / sh["name"] / f["name"]).resolve()
|
|
226
|
+
# Path traversal guard: must stay inside export_dir
|
|
227
|
+
if not str(note_subdir).startswith(str(export_dir.resolve())):
|
|
228
|
+
logger.warning(
|
|
229
|
+
"Skipping %s/%s/%s: path traversal detected",
|
|
230
|
+
sh["name"], f["name"], n["name"],
|
|
231
|
+
)
|
|
232
|
+
continue
|
|
231
233
|
note_subdir.mkdir(parents=True, exist_ok=True)
|
|
232
234
|
note_file = note_subdir / f"{n['name']}.md"
|
|
233
235
|
|