basic-memory 0.14.2__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.

Files changed (69) hide show
  1. basic_memory/__init__.py +1 -1
  2. basic_memory/alembic/env.py +3 -1
  3. basic_memory/alembic/versions/a1b2c3d4e5f6_fix_project_foreign_keys.py +53 -0
  4. basic_memory/api/app.py +4 -1
  5. basic_memory/api/routers/management_router.py +3 -1
  6. basic_memory/api/routers/project_router.py +21 -13
  7. basic_memory/api/routers/resource_router.py +3 -3
  8. basic_memory/cli/app.py +3 -3
  9. basic_memory/cli/commands/__init__.py +1 -2
  10. basic_memory/cli/commands/db.py +5 -5
  11. basic_memory/cli/commands/import_chatgpt.py +3 -2
  12. basic_memory/cli/commands/import_claude_conversations.py +3 -1
  13. basic_memory/cli/commands/import_claude_projects.py +3 -1
  14. basic_memory/cli/commands/import_memory_json.py +5 -2
  15. basic_memory/cli/commands/mcp.py +3 -15
  16. basic_memory/cli/commands/project.py +46 -6
  17. basic_memory/cli/commands/status.py +4 -1
  18. basic_memory/cli/commands/sync.py +10 -2
  19. basic_memory/cli/main.py +0 -1
  20. basic_memory/config.py +61 -34
  21. basic_memory/db.py +2 -6
  22. basic_memory/deps.py +3 -2
  23. basic_memory/file_utils.py +65 -0
  24. basic_memory/importers/chatgpt_importer.py +20 -10
  25. basic_memory/importers/memory_json_importer.py +22 -7
  26. basic_memory/importers/utils.py +2 -2
  27. basic_memory/markdown/entity_parser.py +2 -2
  28. basic_memory/markdown/markdown_processor.py +2 -2
  29. basic_memory/markdown/plugins.py +42 -26
  30. basic_memory/markdown/utils.py +1 -1
  31. basic_memory/mcp/async_client.py +22 -2
  32. basic_memory/mcp/project_session.py +6 -4
  33. basic_memory/mcp/prompts/__init__.py +0 -2
  34. basic_memory/mcp/server.py +8 -71
  35. basic_memory/mcp/tools/build_context.py +12 -2
  36. basic_memory/mcp/tools/move_note.py +24 -12
  37. basic_memory/mcp/tools/project_management.py +22 -7
  38. basic_memory/mcp/tools/read_content.py +16 -0
  39. basic_memory/mcp/tools/read_note.py +17 -2
  40. basic_memory/mcp/tools/sync_status.py +3 -2
  41. basic_memory/mcp/tools/write_note.py +9 -1
  42. basic_memory/models/knowledge.py +13 -2
  43. basic_memory/models/project.py +3 -3
  44. basic_memory/repository/entity_repository.py +2 -2
  45. basic_memory/repository/project_repository.py +19 -1
  46. basic_memory/repository/search_repository.py +7 -3
  47. basic_memory/schemas/base.py +40 -10
  48. basic_memory/schemas/importer.py +1 -0
  49. basic_memory/schemas/memory.py +23 -11
  50. basic_memory/services/context_service.py +12 -2
  51. basic_memory/services/directory_service.py +7 -0
  52. basic_memory/services/entity_service.py +56 -10
  53. basic_memory/services/initialization.py +0 -75
  54. basic_memory/services/project_service.py +93 -36
  55. basic_memory/sync/background_sync.py +4 -3
  56. basic_memory/sync/sync_service.py +53 -4
  57. basic_memory/sync/watch_service.py +31 -8
  58. basic_memory/utils.py +234 -71
  59. {basic_memory-0.14.2.dist-info → basic_memory-0.14.4.dist-info}/METADATA +21 -92
  60. {basic_memory-0.14.2.dist-info → basic_memory-0.14.4.dist-info}/RECORD +63 -68
  61. basic_memory/cli/commands/auth.py +0 -136
  62. basic_memory/mcp/auth_provider.py +0 -270
  63. basic_memory/mcp/external_auth_provider.py +0 -321
  64. basic_memory/mcp/prompts/sync_status.py +0 -112
  65. basic_memory/mcp/supabase_auth_provider.py +0 -463
  66. basic_memory/services/migration_service.py +0 -168
  67. {basic_memory-0.14.2.dist-info → basic_memory-0.14.4.dist-info}/WHEEL +0 -0
  68. {basic_memory-0.14.2.dist-info → basic_memory-0.14.4.dist-info}/entry_points.txt +0 -0
  69. {basic_memory-0.14.2.dist-info → basic_memory-0.14.4.dist-info}/licenses/LICENSE +0 -0
basic_memory/utils.py CHANGED
@@ -5,11 +5,12 @@ import os
5
5
  import logging
6
6
  import re
7
7
  import sys
8
- import unicodedata
8
+ from datetime import datetime
9
9
  from pathlib import Path
10
- from typing import Optional, Protocol, Union, runtime_checkable, List, Any
10
+ from typing import Optional, Protocol, Union, runtime_checkable, List
11
11
 
12
12
  from loguru import logger
13
+ from unidecode import unidecode
13
14
 
14
15
 
15
16
  @runtime_checkable
@@ -27,9 +28,11 @@ FilePath = Union[Path, str]
27
28
  logging.getLogger("opentelemetry.sdk.metrics._internal.instrument").setLevel(logging.ERROR)
28
29
 
29
30
 
30
- def generate_permalink(file_path: Union[Path, str, Any]) -> str:
31
- """
32
- Generate a permalink from a file path.
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)
33
36
 
34
37
  Returns:
35
38
  Normalized permalink that matches validation rules. Converts spaces and underscores
@@ -38,7 +41,7 @@ def generate_permalink(file_path: Union[Path, str, Any]) -> str:
38
41
  Examples:
39
42
  >>> generate_permalink("docs/My Feature.md")
40
43
  'docs/my-feature'
41
- >>> generate_permalink("specs/API_v2.md")
44
+ >>> generate_permalink("specs/API (v2).md")
42
45
  'specs/api-v2'
43
46
  >>> generate_permalink("design/unified_model_refactor.md")
44
47
  'design/unified-model-refactor'
@@ -46,84 +49,99 @@ def generate_permalink(file_path: Union[Path, str, Any]) -> str:
46
49
  '中文/测试文档'
47
50
  """
48
51
  # Convert Path to string if needed
49
- path_str = str(file_path)
50
-
51
- # Remove extension
52
- base = os.path.splitext(path_str)[0]
53
-
54
- # Create a transliteration mapping for specific characters
55
- transliteration_map = {
56
- "ø": "o", # Handle Søren -> soren
57
- "å": "a", # Handle Kierkegård -> kierkegard
58
- "ü": "u", # Handle Müller -> muller
59
- "é": "e", # Handle Café -> cafe
60
- "è": "e", # Handle Mère -> mere
61
- "ê": "e", # Handle Fête -> fete
62
- "à": "a", # Handle À la mode -> a la mode
63
- "ç": "c", # Handle Façade -> facade
64
- "ñ": "n", # Handle Niño -> nino
65
- "ö": "o", # Handle Björk -> bjork
66
- "ä": "a", # Handle Häagen -> haagen
67
- # Add more mappings as needed
68
- }
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
+ )
69
67
 
70
- # Process character by character, transliterating Latin characters with diacritics
71
- result = ""
72
- for char in base:
73
- # Direct mapping for known characters
74
- if char.lower() in transliteration_map:
75
- result += transliteration_map[char.lower()]
76
- # General case using Unicode normalization
77
- elif unicodedata.category(char).startswith("L") and ord(char) > 127:
78
- # Decompose the character (e.g., ü -> u + combining diaeresis)
79
- decomposed = unicodedata.normalize("NFD", char)
80
- # If decomposition produced multiple characters and first one is ASCII
81
- if len(decomposed) > 1 and ord(decomposed[0]) < 128:
82
- # Keep only the base character
83
- result += decomposed[0].lower()
84
- else:
85
- # 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
86
78
  result += char
87
- else:
88
- # Add the character as is
89
- result += char
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)
90
85
 
91
- # Handle special punctuation cases for apostrophes
92
- result = result.replace("'", "")
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
+ )
93
94
 
94
- # Insert dash between camelCase
95
- # This regex finds boundaries between lowercase and uppercase letters
96
- 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)
97
97
 
98
- # Insert dash between Chinese and Latin character boundaries
99
- # This is needed for cases like "中文English" -> "中文-english"
100
- result = re.sub(r"([\u4e00-\u9fff])([a-zA-Z])", r"\1-\2", result)
101
- 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)
102
100
 
103
- # Convert ASCII letters to lowercase, preserve non-ASCII characters
104
- lower_text = "".join(c.lower() if c.isascii() and c.isalpha() else c for c in result)
101
+ # Replace underscores with hyphens
102
+ text_with_hyphens = lower_text.replace("_", "-")
105
103
 
106
- # Replace underscores with hyphens
107
- text_with_hyphens = lower_text.replace("_", "-")
104
+ # Remove apostrophes entirely (don't replace with hyphens)
105
+ text_no_apostrophes = text_with_hyphens.replace("'", "")
108
106
 
109
- # Replace spaces and unsafe ASCII characters with hyphens, but preserve non-ASCII characters
110
- # Include common Chinese character ranges and other non-ASCII characters
111
- clean_text = re.sub(
112
- r"[^a-z0-9\u4e00-\u9fff\u3000-\u303f\u3400-\u4dbf/\-]", "-", text_with_hyphens
113
- )
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)
114
130
 
115
131
  # Collapse multiple hyphens
116
132
  clean_text = re.sub(r"-+", "-", clean_text)
117
133
 
118
- # Remove hyphens between adjacent Chinese characters only
119
- # This handles cases like "你好-世界" -> "你好世界"
120
- clean_text = re.sub(r"([\u4e00-\u9fff])-([\u4e00-\u9fff])", r"\1\2", clean_text)
121
-
122
134
  # Clean each path segment
123
135
  segments = clean_text.split("/")
124
136
  clean_segments = [s.strip("-") for s in segments]
125
137
 
126
- return "/".join(clean_segments)
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
127
145
 
128
146
 
129
147
  def setup_logging(
@@ -173,8 +191,6 @@ def setup_logging(
173
191
  "httpx": logging.WARNING,
174
192
  # File watching logs
175
193
  "watchfiles.main": logging.WARNING,
176
- # SQLAlchemy deprecation warnings
177
- "sqlalchemy": logging.WARNING,
178
194
  }
179
195
 
180
196
  # Set log levels for noisy loggers
@@ -203,8 +219,21 @@ def parse_tags(tags: Union[List[str], str, None]) -> List[str]:
203
219
  # First strip whitespace, then strip leading '#' characters to prevent accumulation
204
220
  return [tag.strip().lstrip("#") for tag in tags if tag and tag.strip()]
205
221
 
206
- # Process comma-separated string of tags
222
+ # Process string input
207
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
+
208
237
  # Split by comma, strip whitespace, then strip leading '#' characters
209
238
  return [tag.strip().lstrip("#") for tag in tags.split(",") if tag and tag.strip()]
210
239
 
@@ -214,3 +243,137 @@ def parse_tags(tags: Union[List[str], str, None]) -> List[str]:
214
243
  except (ValueError, TypeError): # pragma: no cover
215
244
  logger.warning(f"Couldn't parse tags from input of type {type(tags)}: {tags}")
216
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
+
260
+ def normalize_file_path_for_comparison(file_path: str) -> str:
261
+ """Normalize a file path for conflict detection.
262
+
263
+ This function normalizes file paths to help detect potential conflicts:
264
+ - Converts to lowercase for case-insensitive comparison
265
+ - Normalizes Unicode characters
266
+ - Handles path separators consistently
267
+
268
+ Args:
269
+ file_path: The file path to normalize
270
+
271
+ Returns:
272
+ Normalized file path for comparison purposes
273
+ """
274
+ import unicodedata
275
+
276
+ # Convert to lowercase for case-insensitive comparison
277
+ normalized = file_path.lower()
278
+
279
+ # Normalize Unicode characters (NFD normalization)
280
+ normalized = unicodedata.normalize("NFD", normalized)
281
+
282
+ # Replace path separators with forward slashes
283
+ normalized = normalized.replace("\\", "/")
284
+
285
+ # Remove multiple slashes
286
+ normalized = re.sub(r"/+", "/", normalized)
287
+
288
+ return normalized
289
+
290
+
291
+ def detect_potential_file_conflicts(file_path: str, existing_paths: List[str]) -> List[str]:
292
+ """Detect potential conflicts between a file path and existing paths.
293
+
294
+ This function checks for various types of conflicts:
295
+ - Case sensitivity differences
296
+ - Unicode normalization differences
297
+ - Path separator differences
298
+ - Permalink generation conflicts
299
+
300
+ Args:
301
+ file_path: The file path to check
302
+ existing_paths: List of existing file paths to check against
303
+
304
+ Returns:
305
+ List of existing paths that might conflict with the given file path
306
+ """
307
+ conflicts = []
308
+
309
+ # Normalize the input file path
310
+ normalized_input = normalize_file_path_for_comparison(file_path)
311
+ input_permalink = generate_permalink(file_path)
312
+
313
+ for existing_path in existing_paths:
314
+ # Skip identical paths
315
+ if existing_path == file_path:
316
+ continue
317
+
318
+ # Check for case-insensitive path conflicts
319
+ normalized_existing = normalize_file_path_for_comparison(existing_path)
320
+ if normalized_input == normalized_existing:
321
+ conflicts.append(existing_path)
322
+ continue
323
+
324
+ # Check for permalink conflicts
325
+ existing_permalink = generate_permalink(existing_path)
326
+ if input_permalink == existing_permalink:
327
+ conflicts.append(existing_path)
328
+ continue
329
+
330
+ return conflicts
331
+
332
+
333
+ def validate_project_path(path: str, project_path: Path) -> bool:
334
+ """Ensure path stays within project boundaries."""
335
+ # Allow empty strings as they resolve to the project root
336
+ if not path:
337
+ return True
338
+
339
+ # Check for obvious path traversal patterns first
340
+ if ".." in path or "~" in path:
341
+ return False
342
+
343
+ # Check for Windows-style path traversal (even on Unix systems)
344
+ if "\\.." in path or path.startswith("\\"):
345
+ return False
346
+
347
+ # Block absolute paths (Unix-style starting with / or Windows-style with drive letters)
348
+ if path.startswith("/") or (len(path) >= 2 and path[1] == ":"):
349
+ return False
350
+
351
+ # Block paths with control characters (but allow whitespace that will be stripped)
352
+ if path.strip() and any(ord(c) < 32 and c not in [" ", "\t"] for c in path):
353
+ return False
354
+
355
+ try:
356
+ resolved = (project_path / path).resolve()
357
+ return resolved.is_relative_to(project_path.resolve())
358
+ except (ValueError, OSError):
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.2
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
@@ -13,7 +13,7 @@ Requires-Dist: aiosqlite>=0.20.0
13
13
  Requires-Dist: alembic>=1.14.1
14
14
  Requires-Dist: dateparser>=1.2.0
15
15
  Requires-Dist: fastapi[standard]>=0.115.8
16
- Requires-Dist: fastmcp<2.10.0,>=2.3.4
16
+ Requires-Dist: fastmcp==2.10.2
17
17
  Requires-Dist: greenlet>=3.1.1
18
18
  Requires-Dist: icecream>=2.1.3
19
19
  Requires-Dist: loguru>=0.7.3
@@ -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://basicmemory.com
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. Note: The Smithery installation uses their hosted MCP server, while your data remains stored locally as Markdown files.
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
- [![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](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 is enabled by default starting with v0.12.0
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
- [![Install with UV in VS Code](https://img.shields.io/badge/VS_Code-UV-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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) [![Install with UV in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-UV-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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](docs/User%20Guide.md#multiple-projects)), update your
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
- Basic Memory will sync the files in your project in real time if you make manual edits.
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
- search_notes(query, page, page_size) - Search across your knowledge base
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