rememble 0.1.1__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.
- rememble-0.1.1/.gitignore +12 -0
- rememble-0.1.1/Dockerfile +15 -0
- rememble-0.1.1/Makefile +40 -0
- rememble-0.1.1/PKG-INFO +124 -0
- rememble-0.1.1/README.md +103 -0
- rememble-0.1.1/docker-compose.yml +6 -0
- rememble-0.1.1/pyproject.toml +56 -0
- rememble-0.1.1/rememble/__init__.py +5 -0
- rememble-0.1.1/rememble/config.py +72 -0
- rememble-0.1.1/rememble/db.py +315 -0
- rememble-0.1.1/rememble/embeddings/__init__.py +6 -0
- rememble-0.1.1/rememble/embeddings/base.py +24 -0
- rememble-0.1.1/rememble/embeddings/compat.py +50 -0
- rememble-0.1.1/rememble/embeddings/factory.py +77 -0
- rememble-0.1.1/rememble/embeddings/local.py +61 -0
- rememble-0.1.1/rememble/embeddings/ollama.py +49 -0
- rememble-0.1.1/rememble/ingest/__init__.py +1 -0
- rememble-0.1.1/rememble/ingest/chunker.py +53 -0
- rememble-0.1.1/rememble/models.py +88 -0
- rememble-0.1.1/rememble/rag/__init__.py +1 -0
- rememble-0.1.1/rememble/rag/context.py +130 -0
- rememble-0.1.1/rememble/search/__init__.py +1 -0
- rememble-0.1.1/rememble/search/fusion.py +142 -0
- rememble-0.1.1/rememble/search/graph.py +147 -0
- rememble-0.1.1/rememble/search/temporal.py +33 -0
- rememble-0.1.1/rememble/search/text.py +77 -0
- rememble-0.1.1/rememble/search/vector.py +49 -0
- rememble-0.1.1/rememble/server.py +462 -0
- rememble-0.1.1/rememble/version.py +10 -0
- rememble-0.1.1/tests/__init__.py +0 -0
- rememble-0.1.1/tests/conftest.py +67 -0
- rememble-0.1.1/tests/test_db.py +117 -0
- rememble-0.1.1/tests/test_embeddings.py +187 -0
- rememble-0.1.1/tests/test_ingest.py +47 -0
- rememble-0.1.1/tests/test_rag.py +53 -0
- rememble-0.1.1/tests/test_search.py +111 -0
- rememble-0.1.1/tests/test_server.py +290 -0
- rememble-0.1.1/uv.lock +2717 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
FROM python:3.12-slim
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
# Install uv for fast dependency resolution
|
|
6
|
+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
|
|
7
|
+
|
|
8
|
+
COPY pyproject.toml .
|
|
9
|
+
COPY src/ src/
|
|
10
|
+
|
|
11
|
+
RUN uv sync --no-dev --frozen 2>/dev/null || uv sync --no-dev
|
|
12
|
+
|
|
13
|
+
ENV REMEMBLE_DB_PATH=/data/memory.db
|
|
14
|
+
|
|
15
|
+
ENTRYPOINT ["uv", "run", "rememble"]
|
rememble-0.1.1/Makefile
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
.PHONY: dev test lint fmt check bump publish release
|
|
2
|
+
|
|
3
|
+
BUMP ?= patch
|
|
4
|
+
|
|
5
|
+
dev: ## Install in development mode
|
|
6
|
+
uv sync --all-extras
|
|
7
|
+
@echo "✓ Development mode ready"
|
|
8
|
+
|
|
9
|
+
test: ## Run tests
|
|
10
|
+
uv run pytest -v
|
|
11
|
+
|
|
12
|
+
lint: ## Lint + type check
|
|
13
|
+
uv run ruff check rememble/ tests/
|
|
14
|
+
uv run basedpyright rememble/
|
|
15
|
+
|
|
16
|
+
fmt: ## Format + fix imports
|
|
17
|
+
uv run ruff format rememble/ tests/
|
|
18
|
+
uv run ruff check --fix rememble/ tests/
|
|
19
|
+
|
|
20
|
+
check: fmt lint test ## Format, lint, type check, test
|
|
21
|
+
|
|
22
|
+
bump: ## Bump version (BUMP=major|minor|patch)
|
|
23
|
+
uv version --bump $(BUMP)
|
|
24
|
+
|
|
25
|
+
publish: ## Build + publish to PyPI
|
|
26
|
+
rm -rf dist/
|
|
27
|
+
uv build
|
|
28
|
+
uv publish
|
|
29
|
+
|
|
30
|
+
release: check ## Full release: fmt, lint, test, bump, tag, push, publish
|
|
31
|
+
@if [ -n "$$(git status --porcelain)" ]; then echo "ERROR: dirty working tree" && exit 1; fi
|
|
32
|
+
uv version --bump $(BUMP)
|
|
33
|
+
$(eval VERSION := $(shell uv version --short))
|
|
34
|
+
git add pyproject.toml uv.lock
|
|
35
|
+
git commit -m "chore(release): v$(VERSION)"
|
|
36
|
+
git tag "v$(VERSION)"
|
|
37
|
+
git push && git push --tags
|
|
38
|
+
$(MAKE) publish
|
|
39
|
+
|
|
40
|
+
.DEFAULT_GOAL := dev
|
rememble-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rememble
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Local MCP memory server with hybrid search, knowledge graph, and RAG
|
|
5
|
+
Author-email: Nik Cubrilovic <git@nikcub.me>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.11
|
|
8
|
+
Requires-Dist: fastmcp>=2.0
|
|
9
|
+
Requires-Dist: httpx>=0.27
|
|
10
|
+
Requires-Dist: pydantic-settings>=2.0
|
|
11
|
+
Requires-Dist: pydantic>=2.0
|
|
12
|
+
Requires-Dist: sqlite-vec>=0.1.6
|
|
13
|
+
Requires-Dist: tiktoken>=0.8
|
|
14
|
+
Provides-Extra: all
|
|
15
|
+
Requires-Dist: onnxruntime>=1.19; extra == 'all'
|
|
16
|
+
Requires-Dist: sentence-transformers>=3.0; extra == 'all'
|
|
17
|
+
Provides-Extra: local
|
|
18
|
+
Requires-Dist: onnxruntime>=1.19; extra == 'local'
|
|
19
|
+
Requires-Dist: sentence-transformers>=3.0; extra == 'local'
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# Rememble
|
|
23
|
+
|
|
24
|
+
Local-first memory server with hybrid search, knowledge graph, and RAG context assembly.
|
|
25
|
+
|
|
26
|
+
SQLite + sqlite-vec + FTS5 backend. Works with any MCP client.
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- Hybrid search: BM25 + vector KNN + temporal scoring (RRF fusion)
|
|
31
|
+
- Knowledge graph: entities, observations, relations
|
|
32
|
+
- Token-budgeted RAG context assembly
|
|
33
|
+
- Multiple embedding providers: Ollama, local (sentence-transformers), OpenAI-compat
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uv sync
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## MCP Client Setup
|
|
42
|
+
|
|
43
|
+
### Claude Code / Claude Desktop
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"mcpServers": {
|
|
48
|
+
"rememble": {
|
|
49
|
+
"command": "uv",
|
|
50
|
+
"args": ["run", "--directory", "/path/to/Rememble", "rememble"]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Configuration
|
|
57
|
+
|
|
58
|
+
Config file: `~/.rememble/config.json` (auto-created on first run).
|
|
59
|
+
|
|
60
|
+
All fields can be overridden via env vars with `REMEMBLE_` prefix and `__` as nested delimiter.
|
|
61
|
+
|
|
62
|
+
| Env var | Default | Description |
|
|
63
|
+
|---------|---------|-------------|
|
|
64
|
+
| `REMEMBLE_DB_PATH` | `~/.rememble/memory.db` | SQLite database path |
|
|
65
|
+
| `REMEMBLE_EMBEDDING__PROVIDER` | `ollama` | `ollama` \| `local` \| `compat` |
|
|
66
|
+
| `REMEMBLE_EMBEDDING__MODEL` | `nomic-embed-text` | Model name for active provider |
|
|
67
|
+
| `REMEMBLE_EMBEDDING__DIMENSIONS` | `768` | Embedding dimensions |
|
|
68
|
+
| `REMEMBLE_EMBEDDING__OLLAMA_URL` | `http://localhost:11434` | Ollama base URL |
|
|
69
|
+
| `REMEMBLE_EMBEDDING__API_TYPE` | `openrouter` | Label for compat provider (logging only) |
|
|
70
|
+
| `REMEMBLE_EMBEDDING__API_ENDPOINT` | `https://openrouter.ai/api/v1` | OpenAI-compat base URL |
|
|
71
|
+
| `REMEMBLE_EMBEDDING__API_KEY` | — | API key (or set `OPENROUTER_API_KEY` / `OPENAI_API_KEY`) |
|
|
72
|
+
|
|
73
|
+
## Embedding Providers
|
|
74
|
+
|
|
75
|
+
### Ollama (default)
|
|
76
|
+
```json
|
|
77
|
+
{ "embedding": { "provider": "ollama", "model": "nomic-embed-text", "dimensions": 768 } }
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Local (sentence-transformers, no network)
|
|
81
|
+
```bash
|
|
82
|
+
uv sync --extra local
|
|
83
|
+
```
|
|
84
|
+
```json
|
|
85
|
+
{ "embedding": { "provider": "local" } }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### OpenAI-compat (OpenRouter, OpenAI, Cohere compat, etc.)
|
|
89
|
+
|
|
90
|
+
**OpenRouter:**
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"embedding": {
|
|
94
|
+
"provider": "compat",
|
|
95
|
+
"api_type": "openrouter",
|
|
96
|
+
"api_endpoint": "https://openrouter.ai/api/v1",
|
|
97
|
+
"model": "openai/text-embedding-3-small",
|
|
98
|
+
"dimensions": 1536
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Cohere via OpenAI compat API:**
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"embedding": {
|
|
107
|
+
"provider": "compat",
|
|
108
|
+
"api_type": "cohere",
|
|
109
|
+
"api_endpoint": "https://api.cohere.com/compatibility/v1",
|
|
110
|
+
"model": "embed-english-light-v3.0",
|
|
111
|
+
"dimensions": 384
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Set `REMEMBLE_EMBEDDING__API_KEY` or `OPENROUTER_API_KEY` / `OPENAI_API_KEY` env var.
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
make dev # install deps
|
|
122
|
+
make test # run tests
|
|
123
|
+
make check # fmt + lint + test
|
|
124
|
+
```
|
rememble-0.1.1/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Rememble
|
|
2
|
+
|
|
3
|
+
Local-first memory server with hybrid search, knowledge graph, and RAG context assembly.
|
|
4
|
+
|
|
5
|
+
SQLite + sqlite-vec + FTS5 backend. Works with any MCP client.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Hybrid search: BM25 + vector KNN + temporal scoring (RRF fusion)
|
|
10
|
+
- Knowledge graph: entities, observations, relations
|
|
11
|
+
- Token-budgeted RAG context assembly
|
|
12
|
+
- Multiple embedding providers: Ollama, local (sentence-transformers), OpenAI-compat
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
uv sync
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## MCP Client Setup
|
|
21
|
+
|
|
22
|
+
### Claude Code / Claude Desktop
|
|
23
|
+
|
|
24
|
+
```json
|
|
25
|
+
{
|
|
26
|
+
"mcpServers": {
|
|
27
|
+
"rememble": {
|
|
28
|
+
"command": "uv",
|
|
29
|
+
"args": ["run", "--directory", "/path/to/Rememble", "rememble"]
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Configuration
|
|
36
|
+
|
|
37
|
+
Config file: `~/.rememble/config.json` (auto-created on first run).
|
|
38
|
+
|
|
39
|
+
All fields can be overridden via env vars with `REMEMBLE_` prefix and `__` as nested delimiter.
|
|
40
|
+
|
|
41
|
+
| Env var | Default | Description |
|
|
42
|
+
|---------|---------|-------------|
|
|
43
|
+
| `REMEMBLE_DB_PATH` | `~/.rememble/memory.db` | SQLite database path |
|
|
44
|
+
| `REMEMBLE_EMBEDDING__PROVIDER` | `ollama` | `ollama` \| `local` \| `compat` |
|
|
45
|
+
| `REMEMBLE_EMBEDDING__MODEL` | `nomic-embed-text` | Model name for active provider |
|
|
46
|
+
| `REMEMBLE_EMBEDDING__DIMENSIONS` | `768` | Embedding dimensions |
|
|
47
|
+
| `REMEMBLE_EMBEDDING__OLLAMA_URL` | `http://localhost:11434` | Ollama base URL |
|
|
48
|
+
| `REMEMBLE_EMBEDDING__API_TYPE` | `openrouter` | Label for compat provider (logging only) |
|
|
49
|
+
| `REMEMBLE_EMBEDDING__API_ENDPOINT` | `https://openrouter.ai/api/v1` | OpenAI-compat base URL |
|
|
50
|
+
| `REMEMBLE_EMBEDDING__API_KEY` | — | API key (or set `OPENROUTER_API_KEY` / `OPENAI_API_KEY`) |
|
|
51
|
+
|
|
52
|
+
## Embedding Providers
|
|
53
|
+
|
|
54
|
+
### Ollama (default)
|
|
55
|
+
```json
|
|
56
|
+
{ "embedding": { "provider": "ollama", "model": "nomic-embed-text", "dimensions": 768 } }
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Local (sentence-transformers, no network)
|
|
60
|
+
```bash
|
|
61
|
+
uv sync --extra local
|
|
62
|
+
```
|
|
63
|
+
```json
|
|
64
|
+
{ "embedding": { "provider": "local" } }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### OpenAI-compat (OpenRouter, OpenAI, Cohere compat, etc.)
|
|
68
|
+
|
|
69
|
+
**OpenRouter:**
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"embedding": {
|
|
73
|
+
"provider": "compat",
|
|
74
|
+
"api_type": "openrouter",
|
|
75
|
+
"api_endpoint": "https://openrouter.ai/api/v1",
|
|
76
|
+
"model": "openai/text-embedding-3-small",
|
|
77
|
+
"dimensions": 1536
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Cohere via OpenAI compat API:**
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"embedding": {
|
|
86
|
+
"provider": "compat",
|
|
87
|
+
"api_type": "cohere",
|
|
88
|
+
"api_endpoint": "https://api.cohere.com/compatibility/v1",
|
|
89
|
+
"model": "embed-english-light-v3.0",
|
|
90
|
+
"dimensions": 384
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Set `REMEMBLE_EMBEDDING__API_KEY` or `OPENROUTER_API_KEY` / `OPENAI_API_KEY` env var.
|
|
96
|
+
|
|
97
|
+
## Development
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
make dev # install deps
|
|
101
|
+
make test # run tests
|
|
102
|
+
make check # fmt + lint + test
|
|
103
|
+
```
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "rememble"
|
|
3
|
+
version = "0.1.1"
|
|
4
|
+
description = "Local MCP memory server with hybrid search, knowledge graph, and RAG"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
authors = [{ name = "Nik Cubrilovic", email = "git@nikcub.me" }]
|
|
8
|
+
requires-python = ">=3.11"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"fastmcp>=2.0",
|
|
11
|
+
"sqlite-vec>=0.1.6",
|
|
12
|
+
"tiktoken>=0.8",
|
|
13
|
+
"pydantic>=2.0",
|
|
14
|
+
"pydantic-settings>=2.0",
|
|
15
|
+
"httpx>=0.27",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
local = ["sentence-transformers>=3.0", "onnxruntime>=1.19"]
|
|
20
|
+
all = ["rememble[local]"]
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
rememble = "rememble.server:main"
|
|
24
|
+
|
|
25
|
+
[build-system]
|
|
26
|
+
requires = ["hatchling"]
|
|
27
|
+
build-backend = "hatchling.build"
|
|
28
|
+
|
|
29
|
+
[tool.hatch.build.targets.wheel]
|
|
30
|
+
packages = ["rememble"]
|
|
31
|
+
|
|
32
|
+
[dependency-groups]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=8.0",
|
|
35
|
+
"pytest-asyncio>=0.24",
|
|
36
|
+
"basedpyright>=1.20",
|
|
37
|
+
"ruff>=0.8",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[tool.ruff]
|
|
41
|
+
target-version = "py311"
|
|
42
|
+
line-length = 100
|
|
43
|
+
|
|
44
|
+
[tool.ruff.lint]
|
|
45
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
46
|
+
|
|
47
|
+
[tool.basedpyright]
|
|
48
|
+
pythonVersion = "3.11"
|
|
49
|
+
typeCheckingMode = "standard"
|
|
50
|
+
reportCallIssue = "warning"
|
|
51
|
+
venvPath = "."
|
|
52
|
+
venv = ".venv"
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
asyncio_mode = "auto"
|
|
56
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Config loading from ~/.rememble/config.json with env var overrides."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import ClassVar, Literal
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
11
|
+
|
|
12
|
+
CONFIG_DIR = Path.home() / ".rememble"
|
|
13
|
+
CONFIG_PATH = CONFIG_DIR / "config.json"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EmbeddingConfig(BaseModel):
|
|
17
|
+
provider: str = "ollama"
|
|
18
|
+
model: str = "nomic-embed-text"
|
|
19
|
+
dimensions: int = 768
|
|
20
|
+
ollama_url: str = "http://localhost:11434"
|
|
21
|
+
# OpenAI-compat settings (provider="compat")
|
|
22
|
+
api_type: str = "openrouter" # label only: openai | openrouter | cohere | etc.
|
|
23
|
+
api_endpoint: str = "https://openrouter.ai/api/v1"
|
|
24
|
+
api_key: str | None = None
|
|
25
|
+
# Local settings
|
|
26
|
+
local_model: str = "sentence-transformers/all-MiniLM-L6-v2"
|
|
27
|
+
local_backend: Literal["torch", "onnx", "openvino"] = "onnx"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SearchConfig(BaseModel):
|
|
31
|
+
default_limit: int = 10
|
|
32
|
+
rrf_k: int = 60
|
|
33
|
+
bm25_weight: float = 0.4
|
|
34
|
+
vector_weight: float = 0.5
|
|
35
|
+
temporal_weight: float = 0.1
|
|
36
|
+
recency_half_life_days: float = 7.0
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class RAGConfig(BaseModel):
|
|
40
|
+
max_context_tokens: int = 1500
|
|
41
|
+
max_snippets: int = 24
|
|
42
|
+
snippet_max_tokens: int = 200
|
|
43
|
+
expansion_max_tokens: int = 600
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ChunkingConfig(BaseModel):
|
|
47
|
+
target_tokens: int = 400
|
|
48
|
+
overlap_tokens: int = 40
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class RemembleConfig(BaseSettings):
|
|
52
|
+
model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict(
|
|
53
|
+
env_prefix="REMEMBLE_",
|
|
54
|
+
env_nested_delimiter="__",
|
|
55
|
+
extra="ignore",
|
|
56
|
+
)
|
|
57
|
+
db_path: str = Field(default_factory=lambda: str(CONFIG_DIR / "memory.db"))
|
|
58
|
+
embedding: EmbeddingConfig = Field(default_factory=EmbeddingConfig)
|
|
59
|
+
search: SearchConfig = Field(default_factory=SearchConfig)
|
|
60
|
+
rag: RAGConfig = Field(default_factory=RAGConfig)
|
|
61
|
+
chunking: ChunkingConfig = Field(default_factory=ChunkingConfig)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def loadConfig() -> RemembleConfig:
|
|
65
|
+
"""Load config from ~/.rememble/config.json with env var overrides."""
|
|
66
|
+
if CONFIG_PATH.exists():
|
|
67
|
+
raw = json.loads(CONFIG_PATH.read_text())
|
|
68
|
+
return RemembleConfig(**raw)
|
|
69
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
70
|
+
config = RemembleConfig()
|
|
71
|
+
CONFIG_PATH.write_text(config.model_dump_json(indent=2))
|
|
72
|
+
return config
|