basic-memory 0.14.3__py3-none-any.whl → 0.14.4__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.
Potentially problematic release.
This version of basic-memory might be problematic. Click here for more details.
- basic_memory/__init__.py +1 -1
- basic_memory/alembic/versions/a1b2c3d4e5f6_fix_project_foreign_keys.py +53 -0
- basic_memory/api/routers/resource_router.py +3 -3
- basic_memory/cli/commands/project.py +9 -10
- basic_memory/config.py +20 -8
- basic_memory/file_utils.py +65 -0
- basic_memory/importers/chatgpt_importer.py +1 -1
- basic_memory/importers/utils.py +2 -2
- basic_memory/markdown/entity_parser.py +2 -2
- basic_memory/markdown/markdown_processor.py +2 -2
- basic_memory/markdown/plugins.py +42 -26
- basic_memory/markdown/utils.py +1 -1
- basic_memory/mcp/tools/build_context.py +12 -2
- basic_memory/mcp/tools/project_management.py +22 -7
- basic_memory/mcp/tools/read_note.py +16 -13
- basic_memory/models/knowledge.py +13 -2
- basic_memory/models/project.py +2 -2
- basic_memory/repository/entity_repository.py +2 -2
- basic_memory/repository/project_repository.py +1 -1
- basic_memory/repository/search_repository.py +7 -3
- basic_memory/schemas/base.py +40 -10
- basic_memory/schemas/memory.py +23 -11
- basic_memory/services/context_service.py +12 -2
- basic_memory/services/directory_service.py +7 -0
- basic_memory/services/entity_service.py +8 -8
- basic_memory/services/project_service.py +11 -11
- basic_memory/sync/sync_service.py +3 -3
- basic_memory/sync/watch_service.py +31 -8
- basic_memory/utils.py +169 -107
- {basic_memory-0.14.3.dist-info → basic_memory-0.14.4.dist-info}/METADATA +20 -91
- {basic_memory-0.14.3.dist-info → basic_memory-0.14.4.dist-info}/RECORD +34 -33
- {basic_memory-0.14.3.dist-info → basic_memory-0.14.4.dist-info}/WHEEL +0 -0
- {basic_memory-0.14.3.dist-info → basic_memory-0.14.4.dist-info}/entry_points.txt +0 -0
- {basic_memory-0.14.3.dist-info → basic_memory-0.14.4.dist-info}/licenses/LICENSE +0 -0
basic_memory/utils.py
CHANGED
|
@@ -4,11 +4,13 @@ import os
|
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
import re
|
|
7
|
-
import
|
|
7
|
+
import sys
|
|
8
|
+
from datetime import datetime
|
|
8
9
|
from pathlib import Path
|
|
9
|
-
from typing import Optional, Protocol, Union, runtime_checkable, List
|
|
10
|
+
from typing import Optional, Protocol, Union, runtime_checkable, List
|
|
10
11
|
|
|
11
12
|
from loguru import logger
|
|
13
|
+
from unidecode import unidecode
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
@runtime_checkable
|
|
@@ -26,9 +28,11 @@ FilePath = Union[Path, str]
|
|
|
26
28
|
logging.getLogger("opentelemetry.sdk.metrics._internal.instrument").setLevel(logging.ERROR)
|
|
27
29
|
|
|
28
30
|
|
|
29
|
-
def generate_permalink(file_path: Union[Path, str,
|
|
30
|
-
"""
|
|
31
|
-
|
|
31
|
+
def generate_permalink(file_path: Union[Path, str, PathLike], split_extension: bool = True) -> str:
|
|
32
|
+
"""Generate a stable permalink from a file path.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
file_path: Original file path (str, Path, or PathLike)
|
|
32
36
|
|
|
33
37
|
Returns:
|
|
34
38
|
Normalized permalink that matches validation rules. Converts spaces and underscores
|
|
@@ -37,7 +41,7 @@ def generate_permalink(file_path: Union[Path, str, Any]) -> str:
|
|
|
37
41
|
Examples:
|
|
38
42
|
>>> generate_permalink("docs/My Feature.md")
|
|
39
43
|
'docs/my-feature'
|
|
40
|
-
>>> generate_permalink("specs/
|
|
44
|
+
>>> generate_permalink("specs/API (v2).md")
|
|
41
45
|
'specs/api-v2'
|
|
42
46
|
>>> generate_permalink("design/unified_model_refactor.md")
|
|
43
47
|
'design/unified-model-refactor'
|
|
@@ -45,84 +49,99 @@ def generate_permalink(file_path: Union[Path, str, Any]) -> str:
|
|
|
45
49
|
'中文/测试文档'
|
|
46
50
|
"""
|
|
47
51
|
# Convert Path to string if needed
|
|
48
|
-
path_str = str(file_path)
|
|
49
|
-
|
|
50
|
-
# Remove extension
|
|
51
|
-
base = os.path.splitext(path_str)
|
|
52
|
-
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"ñ": "n", # Handle Niño -> nino
|
|
64
|
-
"ö": "o", # Handle Björk -> bjork
|
|
65
|
-
"ä": "a", # Handle Häagen -> haagen
|
|
66
|
-
# Add more mappings as needed
|
|
67
|
-
}
|
|
52
|
+
path_str = Path(str(file_path)).as_posix()
|
|
53
|
+
|
|
54
|
+
# Remove extension (for now, possibly)
|
|
55
|
+
(base, extension) = os.path.splitext(path_str)
|
|
56
|
+
|
|
57
|
+
# Check if we have CJK characters that should be preserved
|
|
58
|
+
# CJK ranges: \u4e00-\u9fff (CJK Unified Ideographs), \u3000-\u303f (CJK symbols),
|
|
59
|
+
# \u3400-\u4dbf (CJK Extension A), \uff00-\uffef (Fullwidth forms)
|
|
60
|
+
has_cjk_chars = any(
|
|
61
|
+
"\u4e00" <= char <= "\u9fff"
|
|
62
|
+
or "\u3000" <= char <= "\u303f"
|
|
63
|
+
or "\u3400" <= char <= "\u4dbf"
|
|
64
|
+
or "\uff00" <= char <= "\uffef"
|
|
65
|
+
for char in base
|
|
66
|
+
)
|
|
68
67
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
# If decomposition produced multiple characters and first one is ASCII
|
|
80
|
-
if len(decomposed) > 1 and ord(decomposed[0]) < 128:
|
|
81
|
-
# Keep only the base character
|
|
82
|
-
result += decomposed[0].lower()
|
|
83
|
-
else:
|
|
84
|
-
# For non-Latin scripts like Chinese, preserve the character
|
|
68
|
+
if has_cjk_chars:
|
|
69
|
+
# For text with CJK characters, selectively transliterate only Latin accented chars
|
|
70
|
+
result = ""
|
|
71
|
+
for char in base:
|
|
72
|
+
if (
|
|
73
|
+
"\u4e00" <= char <= "\u9fff"
|
|
74
|
+
or "\u3000" <= char <= "\u303f"
|
|
75
|
+
or "\u3400" <= char <= "\u4dbf"
|
|
76
|
+
):
|
|
77
|
+
# Preserve CJK ideographs and symbols
|
|
85
78
|
result += char
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
79
|
+
elif "\uff00" <= char <= "\uffef":
|
|
80
|
+
# Remove Chinese fullwidth punctuation entirely (like ,!?)
|
|
81
|
+
continue
|
|
82
|
+
else:
|
|
83
|
+
# Transliterate Latin accented characters to ASCII
|
|
84
|
+
result += unidecode(char)
|
|
89
85
|
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
# Insert hyphens between CJK and Latin character transitions
|
|
87
|
+
# Match: CJK followed by Latin letter/digit, or Latin letter/digit followed by CJK
|
|
88
|
+
result = re.sub(
|
|
89
|
+
r"([\u4e00-\u9fff\u3000-\u303f\u3400-\u4dbf])([a-zA-Z0-9])", r"\1-\2", result
|
|
90
|
+
)
|
|
91
|
+
result = re.sub(
|
|
92
|
+
r"([a-zA-Z0-9])([\u4e00-\u9fff\u3000-\u303f\u3400-\u4dbf])", r"\1-\2", result
|
|
93
|
+
)
|
|
92
94
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
result = re.sub(r"([a-z0-9])([A-Z])", r"\1-\2", result)
|
|
95
|
+
# Insert dash between camelCase
|
|
96
|
+
result = re.sub(r"([a-z0-9])([A-Z])", r"\1-\2", result)
|
|
96
97
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
result = re.sub(r"([\u4e00-\u9fff])([a-zA-Z])", r"\1-\2", result)
|
|
100
|
-
result = re.sub(r"([a-zA-Z])([\u4e00-\u9fff])", r"\1-\2", result)
|
|
98
|
+
# Convert ASCII letters to lowercase, preserve CJK
|
|
99
|
+
lower_text = "".join(c.lower() if c.isascii() and c.isalpha() else c for c in result)
|
|
101
100
|
|
|
102
|
-
|
|
103
|
-
|
|
101
|
+
# Replace underscores with hyphens
|
|
102
|
+
text_with_hyphens = lower_text.replace("_", "-")
|
|
104
103
|
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
# Remove apostrophes entirely (don't replace with hyphens)
|
|
105
|
+
text_no_apostrophes = text_with_hyphens.replace("'", "")
|
|
107
106
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
107
|
+
# Replace unsafe chars with hyphens, but preserve CJK characters
|
|
108
|
+
clean_text = re.sub(
|
|
109
|
+
r"[^a-z0-9\u4e00-\u9fff\u3000-\u303f\u3400-\u4dbf/\-]", "-", text_no_apostrophes
|
|
110
|
+
)
|
|
111
|
+
else:
|
|
112
|
+
# Original ASCII-only processing for backward compatibility
|
|
113
|
+
# Transliterate unicode to ascii
|
|
114
|
+
ascii_text = unidecode(base)
|
|
115
|
+
|
|
116
|
+
# Insert dash between camelCase
|
|
117
|
+
ascii_text = re.sub(r"([a-z0-9])([A-Z])", r"\1-\2", ascii_text)
|
|
118
|
+
|
|
119
|
+
# Convert to lowercase
|
|
120
|
+
lower_text = ascii_text.lower()
|
|
121
|
+
|
|
122
|
+
# replace underscores with hyphens
|
|
123
|
+
text_with_hyphens = lower_text.replace("_", "-")
|
|
124
|
+
|
|
125
|
+
# Remove apostrophes entirely (don't replace with hyphens)
|
|
126
|
+
text_no_apostrophes = text_with_hyphens.replace("'", "")
|
|
127
|
+
|
|
128
|
+
# Replace remaining invalid chars with hyphens
|
|
129
|
+
clean_text = re.sub(r"[^a-z0-9/\-]", "-", text_no_apostrophes)
|
|
113
130
|
|
|
114
131
|
# Collapse multiple hyphens
|
|
115
132
|
clean_text = re.sub(r"-+", "-", clean_text)
|
|
116
133
|
|
|
117
|
-
# Remove hyphens between adjacent Chinese characters only
|
|
118
|
-
# This handles cases like "你好-世界" -> "你好世界"
|
|
119
|
-
clean_text = re.sub(r"([\u4e00-\u9fff])-([\u4e00-\u9fff])", r"\1\2", clean_text)
|
|
120
|
-
|
|
121
134
|
# Clean each path segment
|
|
122
135
|
segments = clean_text.split("/")
|
|
123
136
|
clean_segments = [s.strip("-") for s in segments]
|
|
124
137
|
|
|
125
|
-
|
|
138
|
+
return_val = "/".join(clean_segments)
|
|
139
|
+
|
|
140
|
+
# Append file extension back, if necessary
|
|
141
|
+
if not split_extension and extension:
|
|
142
|
+
return_val += extension
|
|
143
|
+
|
|
144
|
+
return return_val
|
|
126
145
|
|
|
127
146
|
|
|
128
147
|
def setup_logging(
|
|
@@ -143,7 +162,7 @@ def setup_logging(
|
|
|
143
162
|
console: Whether to log to the console
|
|
144
163
|
"""
|
|
145
164
|
# Remove default handler and any existing handlers
|
|
146
|
-
|
|
165
|
+
logger.remove()
|
|
147
166
|
|
|
148
167
|
# Add file handler if we are not running tests and a log file is specified
|
|
149
168
|
if log_file and env != "test":
|
|
@@ -161,8 +180,8 @@ def setup_logging(
|
|
|
161
180
|
)
|
|
162
181
|
|
|
163
182
|
# Add console logger if requested or in test mode
|
|
164
|
-
|
|
165
|
-
|
|
183
|
+
if env == "test" or console:
|
|
184
|
+
logger.add(sys.stderr, level=log_level, backtrace=True, diagnose=True, colorize=True)
|
|
166
185
|
|
|
167
186
|
logger.info(f"ENV: '{env}' Log level: '{log_level}' Logging to {log_file}")
|
|
168
187
|
|
|
@@ -172,8 +191,6 @@ def setup_logging(
|
|
|
172
191
|
"httpx": logging.WARNING,
|
|
173
192
|
# File watching logs
|
|
174
193
|
"watchfiles.main": logging.WARNING,
|
|
175
|
-
# SQLAlchemy deprecation warnings
|
|
176
|
-
"sqlalchemy": logging.WARNING,
|
|
177
194
|
}
|
|
178
195
|
|
|
179
196
|
# Set log levels for noisy loggers
|
|
@@ -181,6 +198,65 @@ def setup_logging(
|
|
|
181
198
|
logging.getLogger(logger_name).setLevel(level)
|
|
182
199
|
|
|
183
200
|
|
|
201
|
+
def parse_tags(tags: Union[List[str], str, None]) -> List[str]:
|
|
202
|
+
"""Parse tags from various input formats into a consistent list.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
tags: Can be a list of strings, a comma-separated string, or None
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
A list of tag strings, or an empty list if no tags
|
|
209
|
+
|
|
210
|
+
Note:
|
|
211
|
+
This function strips leading '#' characters from tags to prevent
|
|
212
|
+
their accumulation when tags are processed multiple times.
|
|
213
|
+
"""
|
|
214
|
+
if tags is None:
|
|
215
|
+
return []
|
|
216
|
+
|
|
217
|
+
# Process list of tags
|
|
218
|
+
if isinstance(tags, list):
|
|
219
|
+
# First strip whitespace, then strip leading '#' characters to prevent accumulation
|
|
220
|
+
return [tag.strip().lstrip("#") for tag in tags if tag and tag.strip()]
|
|
221
|
+
|
|
222
|
+
# Process string input
|
|
223
|
+
if isinstance(tags, str):
|
|
224
|
+
# Check if it's a JSON array string (common issue from AI assistants)
|
|
225
|
+
import json
|
|
226
|
+
if tags.strip().startswith('[') and tags.strip().endswith(']'):
|
|
227
|
+
try:
|
|
228
|
+
# Try to parse as JSON array
|
|
229
|
+
parsed_json = json.loads(tags)
|
|
230
|
+
if isinstance(parsed_json, list):
|
|
231
|
+
# Recursively parse the JSON array as a list
|
|
232
|
+
return parse_tags(parsed_json)
|
|
233
|
+
except json.JSONDecodeError:
|
|
234
|
+
# Not valid JSON, fall through to comma-separated parsing
|
|
235
|
+
pass
|
|
236
|
+
|
|
237
|
+
# Split by comma, strip whitespace, then strip leading '#' characters
|
|
238
|
+
return [tag.strip().lstrip("#") for tag in tags.split(",") if tag and tag.strip()]
|
|
239
|
+
|
|
240
|
+
# For any other type, try to convert to string and parse
|
|
241
|
+
try: # pragma: no cover
|
|
242
|
+
return parse_tags(str(tags))
|
|
243
|
+
except (ValueError, TypeError): # pragma: no cover
|
|
244
|
+
logger.warning(f"Couldn't parse tags from input of type {type(tags)}: {tags}")
|
|
245
|
+
return []
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def normalize_newlines(multiline: str) -> str:
|
|
249
|
+
"""Replace any \r\n, \r, or \n with the native newline.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
multiline: String containing any mixture of newlines.
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
A string with normalized newlines native to the platform.
|
|
256
|
+
"""
|
|
257
|
+
return re.sub(r"\r\n?|\n", os.linesep, multiline)
|
|
258
|
+
|
|
259
|
+
|
|
184
260
|
def normalize_file_path_for_comparison(file_path: str) -> str:
|
|
185
261
|
"""Normalize a file path for conflict detection.
|
|
186
262
|
|
|
@@ -254,40 +330,6 @@ def detect_potential_file_conflicts(file_path: str, existing_paths: List[str]) -
|
|
|
254
330
|
return conflicts
|
|
255
331
|
|
|
256
332
|
|
|
257
|
-
def parse_tags(tags: Union[List[str], str, None]) -> List[str]:
|
|
258
|
-
"""Parse tags from various input formats into a consistent list.
|
|
259
|
-
|
|
260
|
-
Args:
|
|
261
|
-
tags: Can be a list of strings, a comma-separated string, or None
|
|
262
|
-
|
|
263
|
-
Returns:
|
|
264
|
-
A list of tag strings, or an empty list if no tags
|
|
265
|
-
|
|
266
|
-
Note:
|
|
267
|
-
This function strips leading '#' characters from tags to prevent
|
|
268
|
-
their accumulation when tags are processed multiple times.
|
|
269
|
-
"""
|
|
270
|
-
if tags is None:
|
|
271
|
-
return []
|
|
272
|
-
|
|
273
|
-
# Process list of tags
|
|
274
|
-
if isinstance(tags, list):
|
|
275
|
-
# First strip whitespace, then strip leading '#' characters to prevent accumulation
|
|
276
|
-
return [tag.strip().lstrip("#") for tag in tags if tag and tag.strip()]
|
|
277
|
-
|
|
278
|
-
# Process comma-separated string of tags
|
|
279
|
-
if isinstance(tags, str):
|
|
280
|
-
# Split by comma, strip whitespace, then strip leading '#' characters
|
|
281
|
-
return [tag.strip().lstrip("#") for tag in tags.split(",") if tag and tag.strip()]
|
|
282
|
-
|
|
283
|
-
# For any other type, try to convert to string and parse
|
|
284
|
-
try: # pragma: no cover
|
|
285
|
-
return parse_tags(str(tags))
|
|
286
|
-
except (ValueError, TypeError): # pragma: no cover
|
|
287
|
-
logger.warning(f"Couldn't parse tags from input of type {type(tags)}: {tags}")
|
|
288
|
-
return []
|
|
289
|
-
|
|
290
|
-
|
|
291
333
|
def validate_project_path(path: str, project_path: Path) -> bool:
|
|
292
334
|
"""Ensure path stays within project boundaries."""
|
|
293
335
|
# Allow empty strings as they resolve to the project root
|
|
@@ -315,3 +357,23 @@ def validate_project_path(path: str, project_path: Path) -> bool:
|
|
|
315
357
|
return resolved.is_relative_to(project_path.resolve())
|
|
316
358
|
except (ValueError, OSError):
|
|
317
359
|
return False
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def ensure_timezone_aware(dt: datetime) -> datetime:
|
|
363
|
+
"""Ensure a datetime is timezone-aware using system timezone.
|
|
364
|
+
|
|
365
|
+
If the datetime is naive, convert it to timezone-aware using the system's local timezone.
|
|
366
|
+
If it's already timezone-aware, return it unchanged.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
dt: The datetime to ensure is timezone-aware
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
A timezone-aware datetime
|
|
373
|
+
"""
|
|
374
|
+
if dt.tzinfo is None:
|
|
375
|
+
# Naive datetime - assume it's in local time and add timezone
|
|
376
|
+
return dt.astimezone()
|
|
377
|
+
else:
|
|
378
|
+
# Already timezone-aware
|
|
379
|
+
return dt
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: basic-memory
|
|
3
|
-
Version: 0.14.
|
|
3
|
+
Version: 0.14.4
|
|
4
4
|
Summary: Local-first knowledge management combining Zettelkasten with knowledge graphs
|
|
5
5
|
Project-URL: Homepage, https://github.com/basicmachines-co/basic-memory
|
|
6
6
|
Project-URL: Repository, https://github.com/basicmachines-co/basic-memory
|
|
@@ -51,11 +51,8 @@ Basic Memory lets you build persistent knowledge through natural conversations w
|
|
|
51
51
|
Claude, while keeping everything in simple Markdown files on your computer. It uses the Model Context Protocol (MCP) to
|
|
52
52
|
enable any compatible LLM to read and write to your local knowledge base.
|
|
53
53
|
|
|
54
|
-
- Website: https://
|
|
55
|
-
- Company: https://basicmachines.co
|
|
54
|
+
- Website: https://basicmachines.co
|
|
56
55
|
- Documentation: https://memory.basicmachines.co
|
|
57
|
-
- Discord: https://discord.gg/tyvKNccgqN
|
|
58
|
-
- YouTube: https://www.youtube.com/@basicmachines-co
|
|
59
56
|
|
|
60
57
|
## Pick up your conversation right where you left off
|
|
61
58
|
|
|
@@ -71,10 +68,6 @@ https://github.com/user-attachments/assets/a55d8238-8dd0-454a-be4c-8860dbbd0ddc
|
|
|
71
68
|
# Install with uv (recommended)
|
|
72
69
|
uv tool install basic-memory
|
|
73
70
|
|
|
74
|
-
# or with Homebrew
|
|
75
|
-
brew tap basicmachines-co/basic-memory
|
|
76
|
-
brew install basic-memory
|
|
77
|
-
|
|
78
71
|
# Configure Claude Desktop (edit ~/Library/Application Support/Claude/claude_desktop_config.json)
|
|
79
72
|
# Add this to your config:
|
|
80
73
|
{
|
|
@@ -106,14 +99,8 @@ Memory for Claude Desktop:
|
|
|
106
99
|
npx -y @smithery/cli install @basicmachines-co/basic-memory --client claude
|
|
107
100
|
```
|
|
108
101
|
|
|
109
|
-
This installs and configures Basic Memory without requiring manual edits to the Claude Desktop configuration file.
|
|
110
|
-
|
|
111
|
-
### Add to Cursor
|
|
112
|
-
|
|
113
|
-
Once you have installed Basic Memory revisit this page for the 1-click installer for Cursor:
|
|
114
|
-
|
|
115
|
-
[](https://cursor.com/install-mcp?name=basic-memory&config=eyJjb21tYW5kIjoiL1VzZXJzL2RyZXcvLmxvY2FsL2Jpbi91dnggYmFzaWMtbWVtb3J5IG1jcCJ9)
|
|
116
|
-
|
|
102
|
+
This installs and configures Basic Memory without requiring manual edits to the Claude Desktop configuration file. The
|
|
103
|
+
Smithery server hosts the MCP server component, while your data remains stored locally as Markdown files.
|
|
117
104
|
|
|
118
105
|
### Glama.ai
|
|
119
106
|
|
|
@@ -204,8 +191,7 @@ The note embeds semantic content and links to other topics via simple Markdown f
|
|
|
204
191
|
|
|
205
192
|
3. You see this file on your computer in real time in the current project directory (default `~/$HOME/basic-memory`).
|
|
206
193
|
|
|
207
|
-
- Realtime sync
|
|
208
|
-
- Project switching during conversations is supported starting with v0.13.0
|
|
194
|
+
- Realtime sync can be enabled via running `basic-memory sync --watch`
|
|
209
195
|
|
|
210
196
|
4. In a chat with the LLM, you can reference a topic:
|
|
211
197
|
|
|
@@ -263,7 +249,7 @@ title: <Entity title>
|
|
|
263
249
|
type: <The type of Entity> (e.g. note)
|
|
264
250
|
permalink: <a uri slug>
|
|
265
251
|
|
|
266
|
-
- <optional metadata> (such as tags)
|
|
252
|
+
- <optional metadata> (such as tags)
|
|
267
253
|
```
|
|
268
254
|
|
|
269
255
|
### Observations
|
|
@@ -315,13 +301,6 @@ Examples of relations:
|
|
|
315
301
|
```
|
|
316
302
|
|
|
317
303
|
## Using with VS Code
|
|
318
|
-
For one-click installation, click one of the install buttons below...
|
|
319
|
-
|
|
320
|
-
[](https://insiders.vscode.dev/redirect/mcp/install?name=basic-memory&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22basic-memory%22%2C%22mcp%22%5D%7D) [](https://insiders.vscode.dev/redirect/mcp/install?name=basic-memory&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22basic-memory%22%2C%22mcp%22%5D%7D&quality=insiders)
|
|
321
|
-
|
|
322
|
-
You can use Basic Memory with VS Code to easily retrieve and store information while coding. Click the installation buttons above for one-click setup, or follow the manual installation instructions below.
|
|
323
|
-
|
|
324
|
-
### Manual Installation
|
|
325
304
|
|
|
326
305
|
Add the following JSON block to your User Settings (JSON) file in VS Code. You can do this by pressing `Ctrl + Shift + P` and typing `Preferences: Open User Settings (JSON)`.
|
|
327
306
|
|
|
@@ -351,6 +330,8 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace
|
|
|
351
330
|
}
|
|
352
331
|
```
|
|
353
332
|
|
|
333
|
+
You can use Basic Memory with VS Code to easily retrieve and store information while coding.
|
|
334
|
+
|
|
354
335
|
## Using with Claude Desktop
|
|
355
336
|
|
|
356
337
|
Basic Memory is built using the MCP (Model Context Protocol) and works with the Claude desktop app (https://claude.ai/):
|
|
@@ -374,8 +355,7 @@ for OS X):
|
|
|
374
355
|
}
|
|
375
356
|
```
|
|
376
357
|
|
|
377
|
-
If you want to use a specific project (see [Multiple Projects](
|
|
378
|
-
Claude Desktop
|
|
358
|
+
If you want to use a specific project (see [Multiple Projects](#multiple-projects) below), update your Claude Desktop
|
|
379
359
|
config:
|
|
380
360
|
|
|
381
361
|
```json
|
|
@@ -385,9 +365,9 @@ config:
|
|
|
385
365
|
"command": "uvx",
|
|
386
366
|
"args": [
|
|
387
367
|
"basic-memory",
|
|
368
|
+
"mcp",
|
|
388
369
|
"--project",
|
|
389
|
-
"your-project-name"
|
|
390
|
-
"mcp"
|
|
370
|
+
"your-project-name"
|
|
391
371
|
]
|
|
392
372
|
}
|
|
393
373
|
}
|
|
@@ -396,27 +376,23 @@ config:
|
|
|
396
376
|
|
|
397
377
|
2. Sync your knowledge:
|
|
398
378
|
|
|
399
|
-
|
|
379
|
+
```bash
|
|
380
|
+
# One-time sync of local knowledge updates
|
|
381
|
+
basic-memory sync
|
|
382
|
+
|
|
383
|
+
# Run realtime sync process (recommended)
|
|
384
|
+
basic-memory sync --watch
|
|
385
|
+
```
|
|
400
386
|
|
|
401
387
|
3. In Claude Desktop, the LLM can now use these tools:
|
|
402
388
|
|
|
403
389
|
```
|
|
404
390
|
write_note(title, content, folder, tags) - Create or update notes
|
|
405
391
|
read_note(identifier, page, page_size) - Read notes by title or permalink
|
|
406
|
-
edit_note(identifier, operation, content) - Edit notes incrementally (append, prepend, find/replace)
|
|
407
|
-
move_note(identifier, destination_path) - Move notes with database consistency
|
|
408
|
-
view_note(identifier) - Display notes as formatted artifacts for better readability
|
|
409
392
|
build_context(url, depth, timeframe) - Navigate knowledge graph via memory:// URLs
|
|
410
|
-
|
|
393
|
+
search(query, page, page_size) - Search across your knowledge base
|
|
411
394
|
recent_activity(type, depth, timeframe) - Find recently updated information
|
|
412
395
|
canvas(nodes, edges, title, folder) - Generate knowledge visualizations
|
|
413
|
-
list_memory_projects() - List all available projects with status
|
|
414
|
-
switch_project(project_name) - Switch to different project context
|
|
415
|
-
get_current_project() - Show current project and statistics
|
|
416
|
-
create_memory_project(name, path, set_default) - Create new projects
|
|
417
|
-
delete_project(name) - Delete projects from configuration
|
|
418
|
-
set_default_project(name) - Set default project
|
|
419
|
-
sync_status() - Check file synchronization status
|
|
420
396
|
```
|
|
421
397
|
|
|
422
398
|
5. Example prompts to try:
|
|
@@ -427,10 +403,6 @@ sync_status() - Check file synchronization status
|
|
|
427
403
|
"Create a canvas visualization of my project components"
|
|
428
404
|
"Read my notes on the authentication system"
|
|
429
405
|
"What have I been working on in the past week?"
|
|
430
|
-
"Switch to my work-notes project"
|
|
431
|
-
"List all my available projects"
|
|
432
|
-
"Edit my coffee brewing note to add a new technique"
|
|
433
|
-
"Move my old meeting notes to the archive folder"
|
|
434
406
|
```
|
|
435
407
|
|
|
436
408
|
## Futher info
|
|
@@ -442,49 +414,6 @@ See the [Documentation](https://memory.basicmachines.co/) for more info, includi
|
|
|
442
414
|
- [Managing multiple Projects](https://memory.basicmachines.co/docs/cli-reference#project)
|
|
443
415
|
- [Importing data from OpenAI/Claude Projects](https://memory.basicmachines.co/docs/cli-reference#import)
|
|
444
416
|
|
|
445
|
-
## Installation Options
|
|
446
|
-
|
|
447
|
-
### Stable Release
|
|
448
|
-
```bash
|
|
449
|
-
pip install basic-memory
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
### Beta/Pre-releases
|
|
453
|
-
```bash
|
|
454
|
-
pip install basic-memory --pre
|
|
455
|
-
```
|
|
456
|
-
|
|
457
|
-
### Development Builds
|
|
458
|
-
Development versions are automatically published on every commit to main with versions like `0.12.4.dev26+468a22f`:
|
|
459
|
-
```bash
|
|
460
|
-
pip install basic-memory --pre --force-reinstall
|
|
461
|
-
```
|
|
462
|
-
|
|
463
|
-
### Docker
|
|
464
|
-
|
|
465
|
-
Run Basic Memory in a container with volume mounting for your Obsidian vault:
|
|
466
|
-
|
|
467
|
-
```bash
|
|
468
|
-
# Clone and start with Docker Compose
|
|
469
|
-
git clone https://github.com/basicmachines-co/basic-memory.git
|
|
470
|
-
cd basic-memory
|
|
471
|
-
|
|
472
|
-
# Edit docker-compose.yml to point to your Obsidian vault
|
|
473
|
-
# Then start the container
|
|
474
|
-
docker-compose up -d
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
Or use Docker directly:
|
|
478
|
-
```bash
|
|
479
|
-
docker run -d \
|
|
480
|
-
--name basic-memory-server \
|
|
481
|
-
-v /path/to/your/obsidian-vault:/data/knowledge:rw \
|
|
482
|
-
-v basic-memory-config:/root/.basic-memory:rw \
|
|
483
|
-
ghcr.io/basicmachines-co/basic-memory:latest
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
See [Docker Setup Guide](docs/Docker.md) for detailed configuration options, multiple project setup, and integration examples.
|
|
487
|
-
|
|
488
417
|
## License
|
|
489
418
|
|
|
490
419
|
AGPL-3.0
|
|
@@ -502,4 +431,4 @@ and submitting PRs.
|
|
|
502
431
|
</picture>
|
|
503
432
|
</a>
|
|
504
433
|
|
|
505
|
-
Built with ♥️ by Basic Machines
|
|
434
|
+
Built with ♥️ by Basic Machines
|