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.
Files changed (55) hide show
  1. neural_memory/__init__.py +38 -0
  2. neural_memory/cli/__init__.py +15 -0
  3. neural_memory/cli/__main__.py +6 -0
  4. neural_memory/cli/config.py +176 -0
  5. neural_memory/cli/main.py +2702 -0
  6. neural_memory/cli/storage.py +169 -0
  7. neural_memory/cli/tui.py +471 -0
  8. neural_memory/core/__init__.py +52 -0
  9. neural_memory/core/brain.py +301 -0
  10. neural_memory/core/brain_mode.py +273 -0
  11. neural_memory/core/fiber.py +236 -0
  12. neural_memory/core/memory_types.py +331 -0
  13. neural_memory/core/neuron.py +168 -0
  14. neural_memory/core/project.py +257 -0
  15. neural_memory/core/synapse.py +215 -0
  16. neural_memory/engine/__init__.py +15 -0
  17. neural_memory/engine/activation.py +335 -0
  18. neural_memory/engine/encoder.py +391 -0
  19. neural_memory/engine/retrieval.py +440 -0
  20. neural_memory/extraction/__init__.py +42 -0
  21. neural_memory/extraction/entities.py +547 -0
  22. neural_memory/extraction/parser.py +337 -0
  23. neural_memory/extraction/router.py +396 -0
  24. neural_memory/extraction/temporal.py +428 -0
  25. neural_memory/mcp/__init__.py +9 -0
  26. neural_memory/mcp/__main__.py +6 -0
  27. neural_memory/mcp/server.py +621 -0
  28. neural_memory/py.typed +0 -0
  29. neural_memory/safety/__init__.py +31 -0
  30. neural_memory/safety/freshness.py +238 -0
  31. neural_memory/safety/sensitive.py +304 -0
  32. neural_memory/server/__init__.py +5 -0
  33. neural_memory/server/app.py +99 -0
  34. neural_memory/server/dependencies.py +33 -0
  35. neural_memory/server/models.py +138 -0
  36. neural_memory/server/routes/__init__.py +7 -0
  37. neural_memory/server/routes/brain.py +221 -0
  38. neural_memory/server/routes/memory.py +169 -0
  39. neural_memory/server/routes/sync.py +387 -0
  40. neural_memory/storage/__init__.py +17 -0
  41. neural_memory/storage/base.py +441 -0
  42. neural_memory/storage/factory.py +329 -0
  43. neural_memory/storage/memory_store.py +896 -0
  44. neural_memory/storage/shared_store.py +650 -0
  45. neural_memory/storage/sqlite_store.py +1613 -0
  46. neural_memory/sync/__init__.py +5 -0
  47. neural_memory/sync/client.py +435 -0
  48. neural_memory/unified_config.py +315 -0
  49. neural_memory/utils/__init__.py +5 -0
  50. neural_memory/utils/config.py +98 -0
  51. neural_memory-0.1.0.dist-info/METADATA +314 -0
  52. neural_memory-0.1.0.dist-info/RECORD +55 -0
  53. neural_memory-0.1.0.dist-info/WHEEL +4 -0
  54. neural_memory-0.1.0.dist-info/entry_points.txt +4 -0
  55. neural_memory-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,428 @@
1
+ """Temporal extraction for Vietnamese and English time expressions."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from collections.abc import Callable
7
+ from dataclasses import dataclass
8
+ from datetime import datetime, timedelta
9
+ from enum import StrEnum
10
+
11
+
12
+ class TimeGranularity(StrEnum):
13
+ """Granularity level of a time reference."""
14
+
15
+ MINUTE = "minute"
16
+ HOUR = "hour"
17
+ DAY = "day"
18
+ WEEK = "week"
19
+ MONTH = "month"
20
+ YEAR = "year"
21
+
22
+
23
+ @dataclass
24
+ class TimeHint:
25
+ """
26
+ A parsed time reference from text.
27
+
28
+ Attributes:
29
+ original: The original text that was matched
30
+ absolute_start: Resolved start datetime
31
+ absolute_end: Resolved end datetime
32
+ granularity: How precise this time reference is
33
+ is_fuzzy: Whether this is an approximate time
34
+ """
35
+
36
+ original: str
37
+ absolute_start: datetime
38
+ absolute_end: datetime
39
+ granularity: TimeGranularity
40
+ is_fuzzy: bool = True
41
+
42
+ @property
43
+ def midpoint(self) -> datetime:
44
+ """Get the midpoint of this time range."""
45
+ delta = (self.absolute_end - self.absolute_start) / 2
46
+ return self.absolute_start + delta
47
+
48
+
49
+ # Type alias for time resolver functions
50
+ TimeResolver = Callable[[datetime], tuple[datetime, datetime]]
51
+
52
+
53
+ def _start_of_day(dt: datetime) -> datetime:
54
+ """Get start of day (00:00:00)."""
55
+ return dt.replace(hour=0, minute=0, second=0, microsecond=0)
56
+
57
+
58
+ def _end_of_day(dt: datetime) -> datetime:
59
+ """Get end of day (23:59:59)."""
60
+ return dt.replace(hour=23, minute=59, second=59, microsecond=999999)
61
+
62
+
63
+ class TemporalExtractor:
64
+ """
65
+ Multi-language temporal expression extractor.
66
+
67
+ Supports Vietnamese and English time expressions.
68
+ """
69
+
70
+ # Vietnamese time patterns
71
+ VI_PATTERNS: dict[str, TimeResolver] = {
72
+ # Relative days
73
+ r"hôm nay": lambda ref: (_start_of_day(ref), _end_of_day(ref)),
74
+ r"hôm qua": lambda ref: (
75
+ _start_of_day(ref - timedelta(days=1)),
76
+ _end_of_day(ref - timedelta(days=1)),
77
+ ),
78
+ r"hôm kia": lambda ref: (
79
+ _start_of_day(ref - timedelta(days=2)),
80
+ _end_of_day(ref - timedelta(days=2)),
81
+ ),
82
+ r"ngày mai": lambda ref: (
83
+ _start_of_day(ref + timedelta(days=1)),
84
+ _end_of_day(ref + timedelta(days=1)),
85
+ ),
86
+ r"ngày kia": lambda ref: (
87
+ _start_of_day(ref + timedelta(days=2)),
88
+ _end_of_day(ref + timedelta(days=2)),
89
+ ),
90
+ # Parts of day (today)
91
+ r"sáng nay": lambda ref: (
92
+ ref.replace(hour=6, minute=0, second=0, microsecond=0),
93
+ ref.replace(hour=12, minute=0, second=0, microsecond=0),
94
+ ),
95
+ r"trưa nay": lambda ref: (
96
+ ref.replace(hour=11, minute=0, second=0, microsecond=0),
97
+ ref.replace(hour=14, minute=0, second=0, microsecond=0),
98
+ ),
99
+ r"chiều nay": lambda ref: (
100
+ ref.replace(hour=14, minute=0, second=0, microsecond=0),
101
+ ref.replace(hour=18, minute=0, second=0, microsecond=0),
102
+ ),
103
+ r"tối nay": lambda ref: (
104
+ ref.replace(hour=18, minute=0, second=0, microsecond=0),
105
+ ref.replace(hour=22, minute=0, second=0, microsecond=0),
106
+ ),
107
+ r"đêm nay": lambda ref: (
108
+ ref.replace(hour=22, minute=0, second=0, microsecond=0),
109
+ (ref + timedelta(days=1)).replace(hour=6, minute=0, second=0, microsecond=0),
110
+ ),
111
+ # Parts of day (yesterday)
112
+ r"sáng qua|sáng hôm qua": lambda ref: (
113
+ (ref - timedelta(days=1)).replace(hour=6, minute=0, second=0, microsecond=0),
114
+ (ref - timedelta(days=1)).replace(hour=12, minute=0, second=0, microsecond=0),
115
+ ),
116
+ r"chiều qua|chiều hôm qua": lambda ref: (
117
+ (ref - timedelta(days=1)).replace(hour=14, minute=0, second=0, microsecond=0),
118
+ (ref - timedelta(days=1)).replace(hour=18, minute=0, second=0, microsecond=0),
119
+ ),
120
+ r"tối qua|tối hôm qua": lambda ref: (
121
+ (ref - timedelta(days=1)).replace(hour=18, minute=0, second=0, microsecond=0),
122
+ (ref - timedelta(days=1)).replace(hour=22, minute=0, second=0, microsecond=0),
123
+ ),
124
+ # Relative weeks
125
+ r"tuần này": lambda ref: (
126
+ _start_of_day(ref - timedelta(days=ref.weekday())),
127
+ _end_of_day(ref - timedelta(days=ref.weekday()) + timedelta(days=6)),
128
+ ),
129
+ r"tuần trước|tuần rồi": lambda ref: (
130
+ _start_of_day(ref - timedelta(days=ref.weekday() + 7)),
131
+ _end_of_day(ref - timedelta(days=ref.weekday() + 1)),
132
+ ),
133
+ r"tuần sau|tuần tới": lambda ref: (
134
+ _start_of_day(ref - timedelta(days=ref.weekday()) + timedelta(days=7)),
135
+ _end_of_day(ref - timedelta(days=ref.weekday()) + timedelta(days=13)),
136
+ ),
137
+ # Relative months
138
+ r"tháng này": lambda ref: (
139
+ ref.replace(day=1, hour=0, minute=0, second=0, microsecond=0),
140
+ _end_of_day(
141
+ (ref.replace(day=28) + timedelta(days=4)).replace(day=1) - timedelta(days=1)
142
+ ),
143
+ ),
144
+ r"tháng trước|tháng rồi": lambda ref: (
145
+ (ref.replace(day=1) - timedelta(days=1)).replace(
146
+ day=1, hour=0, minute=0, second=0, microsecond=0
147
+ ),
148
+ _end_of_day(ref.replace(day=1) - timedelta(days=1)),
149
+ ),
150
+ # Recent time
151
+ r"mới|vừa|vừa xong|mới đây": lambda ref: (
152
+ ref - timedelta(hours=2),
153
+ ref,
154
+ ),
155
+ r"nãy|lúc nãy": lambda ref: (
156
+ ref - timedelta(hours=4),
157
+ ref - timedelta(minutes=30),
158
+ ),
159
+ }
160
+
161
+ # English time patterns
162
+ EN_PATTERNS: dict[str, TimeResolver] = {
163
+ # Relative days
164
+ r"today": lambda ref: (_start_of_day(ref), _end_of_day(ref)),
165
+ r"yesterday": lambda ref: (
166
+ _start_of_day(ref - timedelta(days=1)),
167
+ _end_of_day(ref - timedelta(days=1)),
168
+ ),
169
+ r"day before yesterday": lambda ref: (
170
+ _start_of_day(ref - timedelta(days=2)),
171
+ _end_of_day(ref - timedelta(days=2)),
172
+ ),
173
+ r"tomorrow": lambda ref: (
174
+ _start_of_day(ref + timedelta(days=1)),
175
+ _end_of_day(ref + timedelta(days=1)),
176
+ ),
177
+ # Parts of day (today)
178
+ r"this morning": lambda ref: (
179
+ ref.replace(hour=6, minute=0, second=0, microsecond=0),
180
+ ref.replace(hour=12, minute=0, second=0, microsecond=0),
181
+ ),
182
+ r"this afternoon": lambda ref: (
183
+ ref.replace(hour=12, minute=0, second=0, microsecond=0),
184
+ ref.replace(hour=18, minute=0, second=0, microsecond=0),
185
+ ),
186
+ r"this evening": lambda ref: (
187
+ ref.replace(hour=18, minute=0, second=0, microsecond=0),
188
+ ref.replace(hour=22, minute=0, second=0, microsecond=0),
189
+ ),
190
+ r"tonight": lambda ref: (
191
+ ref.replace(hour=20, minute=0, second=0, microsecond=0),
192
+ (ref + timedelta(days=1)).replace(hour=4, minute=0, second=0, microsecond=0),
193
+ ),
194
+ # Parts of day (yesterday)
195
+ r"yesterday morning": lambda ref: (
196
+ (ref - timedelta(days=1)).replace(hour=6, minute=0, second=0, microsecond=0),
197
+ (ref - timedelta(days=1)).replace(hour=12, minute=0, second=0, microsecond=0),
198
+ ),
199
+ r"yesterday afternoon": lambda ref: (
200
+ (ref - timedelta(days=1)).replace(hour=12, minute=0, second=0, microsecond=0),
201
+ (ref - timedelta(days=1)).replace(hour=18, minute=0, second=0, microsecond=0),
202
+ ),
203
+ r"yesterday evening|last night": lambda ref: (
204
+ (ref - timedelta(days=1)).replace(hour=18, minute=0, second=0, microsecond=0),
205
+ (ref - timedelta(days=1)).replace(hour=23, minute=59, second=59, microsecond=0),
206
+ ),
207
+ # Relative weeks
208
+ r"this week": lambda ref: (
209
+ _start_of_day(ref - timedelta(days=ref.weekday())),
210
+ _end_of_day(ref - timedelta(days=ref.weekday()) + timedelta(days=6)),
211
+ ),
212
+ r"last week": lambda ref: (
213
+ _start_of_day(ref - timedelta(days=ref.weekday() + 7)),
214
+ _end_of_day(ref - timedelta(days=ref.weekday() + 1)),
215
+ ),
216
+ r"next week": lambda ref: (
217
+ _start_of_day(ref - timedelta(days=ref.weekday()) + timedelta(days=7)),
218
+ _end_of_day(ref - timedelta(days=ref.weekday()) + timedelta(days=13)),
219
+ ),
220
+ # Relative months
221
+ r"this month": lambda ref: (
222
+ ref.replace(day=1, hour=0, minute=0, second=0, microsecond=0),
223
+ _end_of_day(
224
+ (ref.replace(day=28) + timedelta(days=4)).replace(day=1) - timedelta(days=1)
225
+ ),
226
+ ),
227
+ r"last month": lambda ref: (
228
+ (ref.replace(day=1) - timedelta(days=1)).replace(
229
+ day=1, hour=0, minute=0, second=0, microsecond=0
230
+ ),
231
+ _end_of_day(ref.replace(day=1) - timedelta(days=1)),
232
+ ),
233
+ # Recent time
234
+ r"just now|just|recently": lambda ref: (
235
+ ref - timedelta(hours=2),
236
+ ref,
237
+ ),
238
+ r"earlier|earlier today": lambda ref: (
239
+ _start_of_day(ref),
240
+ ref - timedelta(hours=1),
241
+ ),
242
+ # Ago patterns
243
+ r"(\d+)\s*minutes?\s*ago": lambda ref, m: (
244
+ ref - timedelta(minutes=int(m.group(1)) + 5),
245
+ ref - timedelta(minutes=max(0, int(m.group(1)) - 5)),
246
+ ),
247
+ r"(\d+)\s*hours?\s*ago": lambda ref, m: (
248
+ ref - timedelta(hours=int(m.group(1)) + 0.5),
249
+ ref - timedelta(hours=max(0, int(m.group(1)) - 0.5)),
250
+ ),
251
+ r"(\d+)\s*days?\s*ago": lambda ref, m: (
252
+ _start_of_day(ref - timedelta(days=int(m.group(1)))),
253
+ _end_of_day(ref - timedelta(days=int(m.group(1)))),
254
+ ),
255
+ }
256
+
257
+ # Vietnamese time patterns with numbers
258
+ VI_NUMBERED_PATTERNS: list[
259
+ tuple[str, Callable[[datetime, re.Match[str]], tuple[datetime, datetime]], TimeGranularity]
260
+ ] = [
261
+ # Hour patterns: "3 giờ", "15h", "3h chiều"
262
+ (
263
+ r"(\d{1,2})\s*(?:giờ|h|g)(?:\s*(sáng|chiều|tối))?",
264
+ lambda ref, m: _resolve_vi_hour(ref, m),
265
+ TimeGranularity.HOUR,
266
+ ),
267
+ # N days ago: "2 ngày trước"
268
+ (
269
+ r"(\d+)\s*ngày\s*(?:trước|qua)",
270
+ lambda ref, m: (
271
+ _start_of_day(ref - timedelta(days=int(m.group(1)))),
272
+ _end_of_day(ref - timedelta(days=int(m.group(1)))),
273
+ ),
274
+ TimeGranularity.DAY,
275
+ ),
276
+ # N weeks ago: "2 tuần trước"
277
+ (
278
+ r"(\d+)\s*tuần\s*(?:trước|qua)",
279
+ lambda ref, m: (
280
+ _start_of_day(ref - timedelta(weeks=int(m.group(1)))),
281
+ _end_of_day(ref - timedelta(weeks=int(m.group(1)) - 1)),
282
+ ),
283
+ TimeGranularity.WEEK,
284
+ ),
285
+ ]
286
+
287
+ def __init__(self) -> None:
288
+ """Initialize the extractor."""
289
+ # Compile regex patterns
290
+ self._vi_compiled = [(re.compile(p, re.IGNORECASE), r) for p, r in self.VI_PATTERNS.items()]
291
+ self._en_compiled = [(re.compile(p, re.IGNORECASE), r) for p, r in self.EN_PATTERNS.items()]
292
+ self._vi_numbered = [
293
+ (re.compile(p, re.IGNORECASE), r, g) for p, r, g in self.VI_NUMBERED_PATTERNS
294
+ ]
295
+
296
+ def extract(
297
+ self,
298
+ text: str,
299
+ reference_time: datetime | None = None,
300
+ language: str = "auto",
301
+ ) -> list[TimeHint]:
302
+ """
303
+ Extract time references from text.
304
+
305
+ Args:
306
+ text: The text to extract from
307
+ reference_time: Reference point for relative times (default: now)
308
+ language: "vi", "en", or "auto" for auto-detection
309
+
310
+ Returns:
311
+ List of TimeHint objects for found time references
312
+ """
313
+ if reference_time is None:
314
+ reference_time = datetime.now()
315
+
316
+ results: list[TimeHint] = []
317
+
318
+ # Determine which patterns to use
319
+ if language == "auto":
320
+ # Use both
321
+ patterns = self._vi_compiled + self._en_compiled
322
+ numbered_patterns = self._vi_numbered
323
+ elif language == "vi":
324
+ patterns = self._vi_compiled
325
+ numbered_patterns = self._vi_numbered
326
+ else: # en
327
+ patterns = self._en_compiled
328
+ numbered_patterns = []
329
+
330
+ # Try each pattern
331
+ for pattern, resolver in patterns:
332
+ for match in pattern.finditer(text):
333
+ try:
334
+ # Check if resolver takes a match argument
335
+ import inspect
336
+
337
+ sig = inspect.signature(resolver)
338
+ if len(sig.parameters) == 2:
339
+ start, end = resolver(reference_time, match) # type: ignore
340
+ else:
341
+ start, end = resolver(reference_time)
342
+
343
+ results.append(
344
+ TimeHint(
345
+ original=match.group(0),
346
+ absolute_start=start,
347
+ absolute_end=end,
348
+ granularity=_infer_granularity(start, end),
349
+ is_fuzzy=True,
350
+ )
351
+ )
352
+ except Exception:
353
+ # Skip patterns that fail to resolve
354
+ continue
355
+
356
+ # Try numbered patterns
357
+ for pattern, resolver, granularity in numbered_patterns:
358
+ for match in pattern.finditer(text):
359
+ try:
360
+ start, end = resolver(reference_time, match)
361
+ results.append(
362
+ TimeHint(
363
+ original=match.group(0),
364
+ absolute_start=start,
365
+ absolute_end=end,
366
+ granularity=granularity,
367
+ is_fuzzy=False,
368
+ )
369
+ )
370
+ except Exception:
371
+ continue
372
+
373
+ # Remove duplicates (same time range)
374
+ seen: set[tuple[datetime, datetime]] = set()
375
+ unique_results: list[TimeHint] = []
376
+ for hint in results:
377
+ key = (hint.absolute_start, hint.absolute_end)
378
+ if key not in seen:
379
+ seen.add(key)
380
+ unique_results.append(hint)
381
+
382
+ return unique_results
383
+
384
+
385
+ def _resolve_vi_hour(ref: datetime, match: re.Match[str]) -> tuple[datetime, datetime]:
386
+ """Resolve Vietnamese hour pattern."""
387
+ hour = int(match.group(1))
388
+ period = match.group(2) # sáng, chiều, tối
389
+
390
+ if period:
391
+ period = period.lower()
392
+ if (period == "chiều" and hour < 12) or (period == "tối" and hour < 12):
393
+ hour += 12
394
+ elif period == "sáng" and hour == 12:
395
+ hour = 0
396
+
397
+ # Assume today if future, yesterday if past
398
+ result = ref.replace(hour=hour, minute=0, second=0, microsecond=0)
399
+ if result > ref:
400
+ # Future time today is fine
401
+ pass
402
+ else:
403
+ # Past time could be today or yesterday
404
+ pass
405
+
406
+ return (
407
+ result - timedelta(minutes=30),
408
+ result + timedelta(minutes=30),
409
+ )
410
+
411
+
412
+ def _infer_granularity(start: datetime, end: datetime) -> TimeGranularity:
413
+ """Infer granularity from time range."""
414
+ delta = end - start
415
+ seconds = delta.total_seconds()
416
+
417
+ if seconds <= 3600: # 1 hour
418
+ return TimeGranularity.MINUTE
419
+ elif seconds <= 86400: # 1 day
420
+ return TimeGranularity.HOUR
421
+ elif seconds <= 604800: # 1 week
422
+ return TimeGranularity.DAY
423
+ elif seconds <= 2678400: # ~1 month
424
+ return TimeGranularity.WEEK
425
+ elif seconds <= 31536000: # 1 year
426
+ return TimeGranularity.MONTH
427
+ else:
428
+ return TimeGranularity.YEAR
@@ -0,0 +1,9 @@
1
+ """MCP (Model Context Protocol) server for NeuralMemory.
2
+
3
+ This module provides an MCP server that exposes NeuralMemory tools
4
+ to Claude Code, Claude Desktop, and other MCP-compatible clients.
5
+ """
6
+
7
+ from neural_memory.mcp.server import create_mcp_server, main, run_mcp_server
8
+
9
+ __all__ = ["create_mcp_server", "main", "run_mcp_server"]
@@ -0,0 +1,6 @@
1
+ """Entry point for running NeuralMemory MCP server."""
2
+
3
+ from neural_memory.mcp.server import main
4
+
5
+ if __name__ == "__main__":
6
+ main()