neural-memory 0.1.0__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.
- neural_memory/__init__.py +38 -0
- neural_memory/cli/__init__.py +15 -0
- neural_memory/cli/__main__.py +6 -0
- neural_memory/cli/config.py +176 -0
- neural_memory/cli/main.py +2702 -0
- neural_memory/cli/storage.py +169 -0
- neural_memory/cli/tui.py +471 -0
- neural_memory/core/__init__.py +52 -0
- neural_memory/core/brain.py +301 -0
- neural_memory/core/brain_mode.py +273 -0
- neural_memory/core/fiber.py +236 -0
- neural_memory/core/memory_types.py +331 -0
- neural_memory/core/neuron.py +168 -0
- neural_memory/core/project.py +257 -0
- neural_memory/core/synapse.py +215 -0
- neural_memory/engine/__init__.py +15 -0
- neural_memory/engine/activation.py +335 -0
- neural_memory/engine/encoder.py +391 -0
- neural_memory/engine/retrieval.py +440 -0
- neural_memory/extraction/__init__.py +42 -0
- neural_memory/extraction/entities.py +547 -0
- neural_memory/extraction/parser.py +337 -0
- neural_memory/extraction/router.py +396 -0
- neural_memory/extraction/temporal.py +428 -0
- neural_memory/mcp/__init__.py +9 -0
- neural_memory/mcp/__main__.py +6 -0
- neural_memory/mcp/server.py +621 -0
- neural_memory/py.typed +0 -0
- neural_memory/safety/__init__.py +31 -0
- neural_memory/safety/freshness.py +238 -0
- neural_memory/safety/sensitive.py +304 -0
- neural_memory/server/__init__.py +5 -0
- neural_memory/server/app.py +99 -0
- neural_memory/server/dependencies.py +33 -0
- neural_memory/server/models.py +138 -0
- neural_memory/server/routes/__init__.py +7 -0
- neural_memory/server/routes/brain.py +221 -0
- neural_memory/server/routes/memory.py +169 -0
- neural_memory/server/routes/sync.py +387 -0
- neural_memory/storage/__init__.py +17 -0
- neural_memory/storage/base.py +441 -0
- neural_memory/storage/factory.py +329 -0
- neural_memory/storage/memory_store.py +896 -0
- neural_memory/storage/shared_store.py +650 -0
- neural_memory/storage/sqlite_store.py +1613 -0
- neural_memory/sync/__init__.py +5 -0
- neural_memory/sync/client.py +435 -0
- neural_memory/unified_config.py +315 -0
- neural_memory/utils/__init__.py +5 -0
- neural_memory/utils/config.py +98 -0
- neural_memory-0.1.0.dist-info/METADATA +314 -0
- neural_memory-0.1.0.dist-info/RECORD +55 -0
- neural_memory-0.1.0.dist-info/WHEEL +4 -0
- neural_memory-0.1.0.dist-info/entry_points.txt +4 -0
- neural_memory-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
"""Unified configuration for NeuralMemory across all tools.
|
|
2
|
+
|
|
3
|
+
This module provides a single configuration system that works across:
|
|
4
|
+
- CLI (nmem command)
|
|
5
|
+
- MCP server (Claude Code, Cursor, AntiGravity)
|
|
6
|
+
- REST API server
|
|
7
|
+
- Any future integrations
|
|
8
|
+
|
|
9
|
+
Configuration is stored in ~/.neuralmemory/config.toml
|
|
10
|
+
Brain data is stored in ~/.neuralmemory/brains/<name>.db (SQLite)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from datetime import datetime
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
# Try to import tomllib (Python 3.11+) or fallback
|
|
22
|
+
try:
|
|
23
|
+
import tomllib
|
|
24
|
+
except ImportError:
|
|
25
|
+
import tomli as tomllib # type: ignore
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_neuralmemory_dir() -> Path:
|
|
29
|
+
"""Get NeuralMemory data directory.
|
|
30
|
+
|
|
31
|
+
Priority:
|
|
32
|
+
1. NEURALMEMORY_DIR environment variable
|
|
33
|
+
2. ~/.neuralmemory/
|
|
34
|
+
"""
|
|
35
|
+
env_dir = os.environ.get("NEURALMEMORY_DIR")
|
|
36
|
+
if env_dir:
|
|
37
|
+
return Path(env_dir)
|
|
38
|
+
return Path.home() / ".neuralmemory"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_default_brain() -> str:
|
|
42
|
+
"""Get default brain name.
|
|
43
|
+
|
|
44
|
+
Priority:
|
|
45
|
+
1. NEURALMEMORY_BRAIN environment variable
|
|
46
|
+
2. "default"
|
|
47
|
+
"""
|
|
48
|
+
return os.environ.get("NEURALMEMORY_BRAIN", "default")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class AutoConfig:
|
|
53
|
+
"""Auto-capture configuration for MCP server."""
|
|
54
|
+
|
|
55
|
+
enabled: bool = False
|
|
56
|
+
capture_decisions: bool = True
|
|
57
|
+
capture_errors: bool = True
|
|
58
|
+
capture_todos: bool = True
|
|
59
|
+
capture_facts: bool = True
|
|
60
|
+
min_confidence: float = 0.7
|
|
61
|
+
|
|
62
|
+
def to_dict(self) -> dict[str, Any]:
|
|
63
|
+
return {
|
|
64
|
+
"enabled": self.enabled,
|
|
65
|
+
"capture_decisions": self.capture_decisions,
|
|
66
|
+
"capture_errors": self.capture_errors,
|
|
67
|
+
"capture_todos": self.capture_todos,
|
|
68
|
+
"capture_facts": self.capture_facts,
|
|
69
|
+
"min_confidence": self.min_confidence,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def from_dict(cls, data: dict[str, Any]) -> AutoConfig:
|
|
74
|
+
return cls(
|
|
75
|
+
enabled=data.get("enabled", False),
|
|
76
|
+
capture_decisions=data.get("capture_decisions", True),
|
|
77
|
+
capture_errors=data.get("capture_errors", True),
|
|
78
|
+
capture_todos=data.get("capture_todos", True),
|
|
79
|
+
capture_facts=data.get("capture_facts", True),
|
|
80
|
+
min_confidence=data.get("min_confidence", 0.7),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@dataclass
|
|
85
|
+
class BrainSettings:
|
|
86
|
+
"""Settings for brain behavior."""
|
|
87
|
+
|
|
88
|
+
decay_rate: float = 0.1
|
|
89
|
+
reinforcement_delta: float = 0.05
|
|
90
|
+
activation_threshold: float = 0.2
|
|
91
|
+
max_spread_hops: int = 4
|
|
92
|
+
max_context_tokens: int = 1500
|
|
93
|
+
|
|
94
|
+
def to_dict(self) -> dict[str, Any]:
|
|
95
|
+
return {
|
|
96
|
+
"decay_rate": self.decay_rate,
|
|
97
|
+
"reinforcement_delta": self.reinforcement_delta,
|
|
98
|
+
"activation_threshold": self.activation_threshold,
|
|
99
|
+
"max_spread_hops": self.max_spread_hops,
|
|
100
|
+
"max_context_tokens": self.max_context_tokens,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@classmethod
|
|
104
|
+
def from_dict(cls, data: dict[str, Any]) -> BrainSettings:
|
|
105
|
+
return cls(
|
|
106
|
+
decay_rate=data.get("decay_rate", 0.1),
|
|
107
|
+
reinforcement_delta=data.get("reinforcement_delta", 0.05),
|
|
108
|
+
activation_threshold=data.get("activation_threshold", 0.2),
|
|
109
|
+
max_spread_hops=data.get("max_spread_hops", 4),
|
|
110
|
+
max_context_tokens=data.get("max_context_tokens", 1500),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass
|
|
115
|
+
class UnifiedConfig:
|
|
116
|
+
"""Unified configuration for NeuralMemory.
|
|
117
|
+
|
|
118
|
+
This configuration is shared across all tools:
|
|
119
|
+
- CLI: nmem commands
|
|
120
|
+
- MCP: Claude Code, Cursor, AntiGravity
|
|
121
|
+
- API: REST server
|
|
122
|
+
|
|
123
|
+
Storage location: ~/.neuralmemory/config.toml
|
|
124
|
+
Brain location: ~/.neuralmemory/brains/<name>.db
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
# Base directory for all NeuralMemory data
|
|
128
|
+
data_dir: Path = field(default_factory=get_neuralmemory_dir)
|
|
129
|
+
|
|
130
|
+
# Current active brain
|
|
131
|
+
current_brain: str = field(default_factory=get_default_brain)
|
|
132
|
+
|
|
133
|
+
# Brain settings
|
|
134
|
+
brain: BrainSettings = field(default_factory=BrainSettings)
|
|
135
|
+
|
|
136
|
+
# Auto-capture settings for MCP
|
|
137
|
+
auto: AutoConfig = field(default_factory=AutoConfig)
|
|
138
|
+
|
|
139
|
+
# CLI preferences
|
|
140
|
+
json_output: bool = False
|
|
141
|
+
default_depth: int | None = None
|
|
142
|
+
default_max_tokens: int = 500
|
|
143
|
+
|
|
144
|
+
# Metadata
|
|
145
|
+
version: str = "1.0"
|
|
146
|
+
updated_at: datetime | None = None
|
|
147
|
+
|
|
148
|
+
@classmethod
|
|
149
|
+
def load(cls, config_path: Path | None = None) -> UnifiedConfig:
|
|
150
|
+
"""Load configuration from file, or create default if doesn't exist."""
|
|
151
|
+
if config_path is None:
|
|
152
|
+
data_dir = get_neuralmemory_dir()
|
|
153
|
+
config_path = data_dir / "config.toml"
|
|
154
|
+
else:
|
|
155
|
+
data_dir = config_path.parent
|
|
156
|
+
|
|
157
|
+
if not config_path.exists():
|
|
158
|
+
# Create default config
|
|
159
|
+
config = cls(data_dir=data_dir)
|
|
160
|
+
config.save()
|
|
161
|
+
return config
|
|
162
|
+
|
|
163
|
+
with open(config_path, "rb") as f:
|
|
164
|
+
data = tomllib.load(f)
|
|
165
|
+
|
|
166
|
+
return cls(
|
|
167
|
+
data_dir=data_dir,
|
|
168
|
+
current_brain=data.get("current_brain", get_default_brain()),
|
|
169
|
+
brain=BrainSettings.from_dict(data.get("brain", {})),
|
|
170
|
+
auto=AutoConfig.from_dict(data.get("auto", {})),
|
|
171
|
+
json_output=data.get("cli", {}).get("json_output", False),
|
|
172
|
+
default_depth=data.get("cli", {}).get("default_depth"),
|
|
173
|
+
default_max_tokens=data.get("cli", {}).get("default_max_tokens", 500),
|
|
174
|
+
version=data.get("version", "1.0"),
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def save(self) -> None:
|
|
178
|
+
"""Save configuration to TOML file."""
|
|
179
|
+
self.data_dir.mkdir(parents=True, exist_ok=True)
|
|
180
|
+
config_path = self.data_dir / "config.toml"
|
|
181
|
+
|
|
182
|
+
# Build TOML content manually (no toml write dependency)
|
|
183
|
+
lines = [
|
|
184
|
+
"# NeuralMemory Configuration",
|
|
185
|
+
"# This config is shared by CLI, MCP server, and all integrations",
|
|
186
|
+
"",
|
|
187
|
+
f'version = "{self.version}"',
|
|
188
|
+
f'current_brain = "{self.current_brain}"',
|
|
189
|
+
"",
|
|
190
|
+
"# Brain behavior settings",
|
|
191
|
+
"[brain]",
|
|
192
|
+
f"decay_rate = {self.brain.decay_rate}",
|
|
193
|
+
f"reinforcement_delta = {self.brain.reinforcement_delta}",
|
|
194
|
+
f"activation_threshold = {self.brain.activation_threshold}",
|
|
195
|
+
f"max_spread_hops = {self.brain.max_spread_hops}",
|
|
196
|
+
f"max_context_tokens = {self.brain.max_context_tokens}",
|
|
197
|
+
"",
|
|
198
|
+
"# Auto-capture settings for MCP server",
|
|
199
|
+
"[auto]",
|
|
200
|
+
f"enabled = {'true' if self.auto.enabled else 'false'}",
|
|
201
|
+
f"capture_decisions = {'true' if self.auto.capture_decisions else 'false'}",
|
|
202
|
+
f"capture_errors = {'true' if self.auto.capture_errors else 'false'}",
|
|
203
|
+
f"capture_todos = {'true' if self.auto.capture_todos else 'false'}",
|
|
204
|
+
f"capture_facts = {'true' if self.auto.capture_facts else 'false'}",
|
|
205
|
+
f"min_confidence = {self.auto.min_confidence}",
|
|
206
|
+
"",
|
|
207
|
+
"# CLI preferences",
|
|
208
|
+
"[cli]",
|
|
209
|
+
f"json_output = {'true' if self.json_output else 'false'}",
|
|
210
|
+
f"default_max_tokens = {self.default_max_tokens}",
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
if self.default_depth is not None:
|
|
214
|
+
lines.append(f"default_depth = {self.default_depth}")
|
|
215
|
+
|
|
216
|
+
with open(config_path, "w", encoding="utf-8") as f:
|
|
217
|
+
f.write("\n".join(lines) + "\n")
|
|
218
|
+
|
|
219
|
+
@property
|
|
220
|
+
def brains_dir(self) -> Path:
|
|
221
|
+
"""Get directory where brain databases are stored."""
|
|
222
|
+
return self.data_dir / "brains"
|
|
223
|
+
|
|
224
|
+
@property
|
|
225
|
+
def config_path(self) -> Path:
|
|
226
|
+
"""Get path to config file."""
|
|
227
|
+
return self.data_dir / "config.toml"
|
|
228
|
+
|
|
229
|
+
def get_brain_db_path(self, brain_name: str | None = None) -> Path:
|
|
230
|
+
"""Get path to brain SQLite database.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
brain_name: Brain name, or use current_brain if None
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
Path to SQLite database file
|
|
237
|
+
"""
|
|
238
|
+
name = brain_name or self.current_brain
|
|
239
|
+
return self.brains_dir / f"{name}.db"
|
|
240
|
+
|
|
241
|
+
def list_brains(self) -> list[str]:
|
|
242
|
+
"""List available brains (by database files)."""
|
|
243
|
+
if not self.brains_dir.exists():
|
|
244
|
+
return []
|
|
245
|
+
return [p.stem for p in self.brains_dir.glob("*.db")]
|
|
246
|
+
|
|
247
|
+
def switch_brain(self, brain_name: str) -> None:
|
|
248
|
+
"""Switch to a different brain and save config."""
|
|
249
|
+
self.current_brain = brain_name
|
|
250
|
+
self.save()
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# Singleton instance for easy access
|
|
254
|
+
_config: UnifiedConfig | None = None
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def get_config(reload: bool = False) -> UnifiedConfig:
|
|
258
|
+
"""Get the unified configuration (singleton).
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
reload: Force reload from disk
|
|
262
|
+
|
|
263
|
+
Returns:
|
|
264
|
+
UnifiedConfig instance
|
|
265
|
+
"""
|
|
266
|
+
global _config
|
|
267
|
+
if _config is None or reload:
|
|
268
|
+
_config = UnifiedConfig.load()
|
|
269
|
+
return _config
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
async def get_shared_storage(brain_name: str | None = None):
|
|
273
|
+
"""Get SQLite storage for shared brain access.
|
|
274
|
+
|
|
275
|
+
This is the main entry point for getting storage that works
|
|
276
|
+
across CLI, MCP, and other tools.
|
|
277
|
+
|
|
278
|
+
Args:
|
|
279
|
+
brain_name: Brain name, or use config's current_brain if None
|
|
280
|
+
|
|
281
|
+
Returns:
|
|
282
|
+
SQLiteStorage instance, initialized and ready to use
|
|
283
|
+
"""
|
|
284
|
+
from neural_memory.core.brain import Brain
|
|
285
|
+
from neural_memory.storage.sqlite_store import SQLiteStorage
|
|
286
|
+
|
|
287
|
+
config = get_config()
|
|
288
|
+
db_path = config.get_brain_db_path(brain_name)
|
|
289
|
+
|
|
290
|
+
# Ensure directory exists
|
|
291
|
+
db_path.parent.mkdir(parents=True, exist_ok=True)
|
|
292
|
+
|
|
293
|
+
# Create and initialize storage
|
|
294
|
+
storage = SQLiteStorage(db_path)
|
|
295
|
+
await storage.initialize()
|
|
296
|
+
|
|
297
|
+
# Create brain if it doesn't exist
|
|
298
|
+
name = brain_name or config.current_brain
|
|
299
|
+
brain = await storage.get_brain(name)
|
|
300
|
+
|
|
301
|
+
if brain is None:
|
|
302
|
+
from neural_memory.core.brain import BrainConfig
|
|
303
|
+
|
|
304
|
+
brain_config = BrainConfig(
|
|
305
|
+
decay_rate=config.brain.decay_rate,
|
|
306
|
+
reinforcement_delta=config.brain.reinforcement_delta,
|
|
307
|
+
activation_threshold=config.brain.activation_threshold,
|
|
308
|
+
max_spread_hops=config.brain.max_spread_hops,
|
|
309
|
+
max_context_tokens=config.brain.max_context_tokens,
|
|
310
|
+
)
|
|
311
|
+
brain = Brain.create(name=name, config=brain_config, brain_id=name)
|
|
312
|
+
await storage.save_brain(brain)
|
|
313
|
+
|
|
314
|
+
storage.set_brain(brain.id)
|
|
315
|
+
return storage
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Configuration management for NeuralMemory."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class Config:
|
|
11
|
+
"""
|
|
12
|
+
Application configuration.
|
|
13
|
+
|
|
14
|
+
Loaded from environment variables with sensible defaults.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
# Server settings
|
|
18
|
+
host: str = "0.0.0.0"
|
|
19
|
+
port: int = 8000
|
|
20
|
+
debug: bool = False
|
|
21
|
+
|
|
22
|
+
# Storage settings
|
|
23
|
+
storage_backend: str = "memory" # memory, sqlite, neo4j
|
|
24
|
+
sqlite_path: str | None = None
|
|
25
|
+
neo4j_uri: str | None = None
|
|
26
|
+
neo4j_user: str | None = None
|
|
27
|
+
neo4j_password: str | None = None
|
|
28
|
+
|
|
29
|
+
# Brain defaults
|
|
30
|
+
default_decay_rate: float = 0.1
|
|
31
|
+
default_activation_threshold: float = 0.2
|
|
32
|
+
default_max_spread_hops: int = 4
|
|
33
|
+
default_max_context_tokens: int = 1500
|
|
34
|
+
|
|
35
|
+
# CORS settings
|
|
36
|
+
cors_origins: list[str] = field(default_factory=lambda: ["*"])
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def from_env(cls) -> Config:
|
|
40
|
+
"""Load configuration from environment variables."""
|
|
41
|
+
|
|
42
|
+
def get_bool(key: str, default: bool) -> bool:
|
|
43
|
+
value = os.getenv(key)
|
|
44
|
+
if value is None:
|
|
45
|
+
return default
|
|
46
|
+
return value.lower() in ("true", "1", "yes")
|
|
47
|
+
|
|
48
|
+
def get_int(key: str, default: int) -> int:
|
|
49
|
+
value = os.getenv(key)
|
|
50
|
+
if value is None:
|
|
51
|
+
return default
|
|
52
|
+
return int(value)
|
|
53
|
+
|
|
54
|
+
def get_float(key: str, default: float) -> float:
|
|
55
|
+
value = os.getenv(key)
|
|
56
|
+
if value is None:
|
|
57
|
+
return default
|
|
58
|
+
return float(value)
|
|
59
|
+
|
|
60
|
+
def get_list(key: str, default: list[str]) -> list[str]:
|
|
61
|
+
value = os.getenv(key)
|
|
62
|
+
if value is None:
|
|
63
|
+
return default
|
|
64
|
+
return [s.strip() for s in value.split(",")]
|
|
65
|
+
|
|
66
|
+
return cls(
|
|
67
|
+
host=os.getenv("NEURAL_MEMORY_HOST", "0.0.0.0"),
|
|
68
|
+
port=get_int("NEURAL_MEMORY_PORT", 8000),
|
|
69
|
+
debug=get_bool("NEURAL_MEMORY_DEBUG", False),
|
|
70
|
+
storage_backend=os.getenv("NEURAL_MEMORY_STORAGE", "memory"),
|
|
71
|
+
sqlite_path=os.getenv("NEURAL_MEMORY_SQLITE_PATH"),
|
|
72
|
+
neo4j_uri=os.getenv("NEURAL_MEMORY_NEO4J_URI"),
|
|
73
|
+
neo4j_user=os.getenv("NEURAL_MEMORY_NEO4J_USER"),
|
|
74
|
+
neo4j_password=os.getenv("NEURAL_MEMORY_NEO4J_PASSWORD"),
|
|
75
|
+
default_decay_rate=get_float("NEURAL_MEMORY_DECAY_RATE", 0.1),
|
|
76
|
+
default_activation_threshold=get_float("NEURAL_MEMORY_ACTIVATION_THRESHOLD", 0.2),
|
|
77
|
+
default_max_spread_hops=get_int("NEURAL_MEMORY_MAX_SPREAD_HOPS", 4),
|
|
78
|
+
default_max_context_tokens=get_int("NEURAL_MEMORY_MAX_CONTEXT_TOKENS", 1500),
|
|
79
|
+
cors_origins=get_list("NEURAL_MEMORY_CORS_ORIGINS", ["*"]),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# Singleton config instance
|
|
84
|
+
_config: Config | None = None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def get_config() -> Config:
|
|
88
|
+
"""Get the global configuration instance."""
|
|
89
|
+
global _config
|
|
90
|
+
if _config is None:
|
|
91
|
+
_config = Config.from_env()
|
|
92
|
+
return _config
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def reset_config() -> None:
|
|
96
|
+
"""Reset the global configuration (useful for testing)."""
|
|
97
|
+
global _config
|
|
98
|
+
_config = None
|