basic-memory 0.7.0__py3-none-any.whl → 0.16.1__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 (150) hide show
  1. basic_memory/__init__.py +5 -1
  2. basic_memory/alembic/alembic.ini +119 -0
  3. basic_memory/alembic/env.py +27 -3
  4. basic_memory/alembic/migrations.py +4 -9
  5. basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py +51 -0
  6. basic_memory/alembic/versions/5fe1ab1ccebe_add_projects_table.py +108 -0
  7. basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py +104 -0
  8. basic_memory/alembic/versions/9d9c1cb7d8f5_add_mtime_and_size_columns_to_entity_.py +49 -0
  9. basic_memory/alembic/versions/a1b2c3d4e5f6_fix_project_foreign_keys.py +49 -0
  10. basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py +44 -0
  11. basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +100 -0
  12. basic_memory/alembic/versions/e7e1f4367280_add_scan_watermark_tracking_to_project.py +37 -0
  13. basic_memory/api/app.py +64 -18
  14. basic_memory/api/routers/__init__.py +4 -1
  15. basic_memory/api/routers/directory_router.py +84 -0
  16. basic_memory/api/routers/importer_router.py +152 -0
  17. basic_memory/api/routers/knowledge_router.py +166 -21
  18. basic_memory/api/routers/management_router.py +80 -0
  19. basic_memory/api/routers/memory_router.py +9 -64
  20. basic_memory/api/routers/project_router.py +406 -0
  21. basic_memory/api/routers/prompt_router.py +260 -0
  22. basic_memory/api/routers/resource_router.py +119 -4
  23. basic_memory/api/routers/search_router.py +5 -5
  24. basic_memory/api/routers/utils.py +130 -0
  25. basic_memory/api/template_loader.py +292 -0
  26. basic_memory/cli/app.py +43 -9
  27. basic_memory/cli/auth.py +277 -0
  28. basic_memory/cli/commands/__init__.py +13 -2
  29. basic_memory/cli/commands/cloud/__init__.py +6 -0
  30. basic_memory/cli/commands/cloud/api_client.py +112 -0
  31. basic_memory/cli/commands/cloud/bisync_commands.py +110 -0
  32. basic_memory/cli/commands/cloud/cloud_utils.py +101 -0
  33. basic_memory/cli/commands/cloud/core_commands.py +195 -0
  34. basic_memory/cli/commands/cloud/rclone_commands.py +301 -0
  35. basic_memory/cli/commands/cloud/rclone_config.py +110 -0
  36. basic_memory/cli/commands/cloud/rclone_installer.py +249 -0
  37. basic_memory/cli/commands/cloud/upload.py +233 -0
  38. basic_memory/cli/commands/cloud/upload_command.py +124 -0
  39. basic_memory/cli/commands/command_utils.py +51 -0
  40. basic_memory/cli/commands/db.py +28 -12
  41. basic_memory/cli/commands/import_chatgpt.py +40 -220
  42. basic_memory/cli/commands/import_claude_conversations.py +41 -168
  43. basic_memory/cli/commands/import_claude_projects.py +46 -157
  44. basic_memory/cli/commands/import_memory_json.py +48 -108
  45. basic_memory/cli/commands/mcp.py +84 -10
  46. basic_memory/cli/commands/project.py +876 -0
  47. basic_memory/cli/commands/status.py +50 -33
  48. basic_memory/cli/commands/tool.py +341 -0
  49. basic_memory/cli/main.py +8 -7
  50. basic_memory/config.py +477 -23
  51. basic_memory/db.py +168 -17
  52. basic_memory/deps.py +251 -25
  53. basic_memory/file_utils.py +113 -58
  54. basic_memory/ignore_utils.py +297 -0
  55. basic_memory/importers/__init__.py +27 -0
  56. basic_memory/importers/base.py +79 -0
  57. basic_memory/importers/chatgpt_importer.py +232 -0
  58. basic_memory/importers/claude_conversations_importer.py +177 -0
  59. basic_memory/importers/claude_projects_importer.py +148 -0
  60. basic_memory/importers/memory_json_importer.py +108 -0
  61. basic_memory/importers/utils.py +58 -0
  62. basic_memory/markdown/entity_parser.py +143 -23
  63. basic_memory/markdown/markdown_processor.py +3 -3
  64. basic_memory/markdown/plugins.py +39 -21
  65. basic_memory/markdown/schemas.py +1 -1
  66. basic_memory/markdown/utils.py +28 -13
  67. basic_memory/mcp/async_client.py +134 -4
  68. basic_memory/mcp/project_context.py +141 -0
  69. basic_memory/mcp/prompts/__init__.py +19 -0
  70. basic_memory/mcp/prompts/ai_assistant_guide.py +70 -0
  71. basic_memory/mcp/prompts/continue_conversation.py +62 -0
  72. basic_memory/mcp/prompts/recent_activity.py +188 -0
  73. basic_memory/mcp/prompts/search.py +57 -0
  74. basic_memory/mcp/prompts/utils.py +162 -0
  75. basic_memory/mcp/resources/ai_assistant_guide.md +283 -0
  76. basic_memory/mcp/resources/project_info.py +71 -0
  77. basic_memory/mcp/server.py +7 -13
  78. basic_memory/mcp/tools/__init__.py +33 -21
  79. basic_memory/mcp/tools/build_context.py +120 -0
  80. basic_memory/mcp/tools/canvas.py +130 -0
  81. basic_memory/mcp/tools/chatgpt_tools.py +187 -0
  82. basic_memory/mcp/tools/delete_note.py +225 -0
  83. basic_memory/mcp/tools/edit_note.py +320 -0
  84. basic_memory/mcp/tools/list_directory.py +167 -0
  85. basic_memory/mcp/tools/move_note.py +545 -0
  86. basic_memory/mcp/tools/project_management.py +200 -0
  87. basic_memory/mcp/tools/read_content.py +271 -0
  88. basic_memory/mcp/tools/read_note.py +255 -0
  89. basic_memory/mcp/tools/recent_activity.py +534 -0
  90. basic_memory/mcp/tools/search.py +369 -23
  91. basic_memory/mcp/tools/utils.py +374 -16
  92. basic_memory/mcp/tools/view_note.py +77 -0
  93. basic_memory/mcp/tools/write_note.py +207 -0
  94. basic_memory/models/__init__.py +3 -2
  95. basic_memory/models/knowledge.py +67 -15
  96. basic_memory/models/project.py +87 -0
  97. basic_memory/models/search.py +10 -6
  98. basic_memory/repository/__init__.py +2 -0
  99. basic_memory/repository/entity_repository.py +229 -7
  100. basic_memory/repository/observation_repository.py +35 -3
  101. basic_memory/repository/project_info_repository.py +10 -0
  102. basic_memory/repository/project_repository.py +103 -0
  103. basic_memory/repository/relation_repository.py +21 -2
  104. basic_memory/repository/repository.py +147 -29
  105. basic_memory/repository/search_repository.py +411 -62
  106. basic_memory/schemas/__init__.py +22 -9
  107. basic_memory/schemas/base.py +97 -8
  108. basic_memory/schemas/cloud.py +50 -0
  109. basic_memory/schemas/directory.py +30 -0
  110. basic_memory/schemas/importer.py +35 -0
  111. basic_memory/schemas/memory.py +187 -25
  112. basic_memory/schemas/project_info.py +211 -0
  113. basic_memory/schemas/prompt.py +90 -0
  114. basic_memory/schemas/request.py +56 -2
  115. basic_memory/schemas/response.py +1 -1
  116. basic_memory/schemas/search.py +31 -35
  117. basic_memory/schemas/sync_report.py +72 -0
  118. basic_memory/services/__init__.py +2 -1
  119. basic_memory/services/context_service.py +241 -104
  120. basic_memory/services/directory_service.py +295 -0
  121. basic_memory/services/entity_service.py +590 -60
  122. basic_memory/services/exceptions.py +21 -0
  123. basic_memory/services/file_service.py +284 -30
  124. basic_memory/services/initialization.py +191 -0
  125. basic_memory/services/link_resolver.py +49 -56
  126. basic_memory/services/project_service.py +863 -0
  127. basic_memory/services/search_service.py +168 -32
  128. basic_memory/sync/__init__.py +3 -2
  129. basic_memory/sync/background_sync.py +26 -0
  130. basic_memory/sync/sync_service.py +1180 -109
  131. basic_memory/sync/watch_service.py +412 -135
  132. basic_memory/templates/prompts/continue_conversation.hbs +110 -0
  133. basic_memory/templates/prompts/search.hbs +101 -0
  134. basic_memory/utils.py +383 -51
  135. basic_memory-0.16.1.dist-info/METADATA +493 -0
  136. basic_memory-0.16.1.dist-info/RECORD +148 -0
  137. {basic_memory-0.7.0.dist-info → basic_memory-0.16.1.dist-info}/entry_points.txt +1 -0
  138. basic_memory/alembic/README +0 -1
  139. basic_memory/cli/commands/sync.py +0 -206
  140. basic_memory/cli/commands/tools.py +0 -157
  141. basic_memory/mcp/tools/knowledge.py +0 -68
  142. basic_memory/mcp/tools/memory.py +0 -170
  143. basic_memory/mcp/tools/notes.py +0 -202
  144. basic_memory/schemas/discovery.py +0 -28
  145. basic_memory/sync/file_change_scanner.py +0 -158
  146. basic_memory/sync/utils.py +0 -31
  147. basic_memory-0.7.0.dist-info/METADATA +0 -378
  148. basic_memory-0.7.0.dist-info/RECORD +0 -82
  149. {basic_memory-0.7.0.dist-info → basic_memory-0.16.1.dist-info}/WHEEL +0 -0
  150. {basic_memory-0.7.0.dist-info → basic_memory-0.16.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,14 +1,13 @@
1
1
  """Service for resolving markdown links to permalinks."""
2
2
 
3
- from typing import Optional, Tuple, List
3
+ from typing import Optional, Tuple
4
4
 
5
5
  from loguru import logger
6
6
 
7
- from basic_memory.repository.entity_repository import EntityRepository
8
- from basic_memory.repository.search_repository import SearchIndexRow
9
- from basic_memory.services.search_service import SearchService
10
7
  from basic_memory.models import Entity
8
+ from basic_memory.repository.entity_repository import EntityRepository
11
9
  from basic_memory.schemas.search import SearchQuery, SearchItemType
10
+ from basic_memory.services.search_service import SearchService
12
11
 
13
12
 
14
13
  class LinkResolver:
@@ -16,10 +15,10 @@ class LinkResolver:
16
15
 
17
16
  Uses a combination of exact matching and search-based resolution:
18
17
  1. Try exact permalink match (fastest)
19
- 2. Try permalink pattern match (for wildcards)
20
- 3. Try exact title match
21
- 4. Fall back to search for fuzzy matching
22
- 5. Generate new permalink if no match found
18
+ 2. Try exact title match
19
+ 3. Try exact file path match
20
+ 4. Try file path with .md extension (for folder/title patterns)
21
+ 5. Fall back to search for fuzzy matching
23
22
  """
24
23
 
25
24
  def __init__(self, entity_repository: EntityRepository, search_service: SearchService):
@@ -27,9 +26,17 @@ class LinkResolver:
27
26
  self.entity_repository = entity_repository
28
27
  self.search_service = search_service
29
28
 
30
- async def resolve_link(self, link_text: str, use_search: bool = True) -> Optional[Entity]:
31
- """Resolve a markdown link to a permalink."""
32
- logger.debug(f"Resolving link: {link_text}")
29
+ async def resolve_link(
30
+ self, link_text: str, use_search: bool = True, strict: bool = False
31
+ ) -> Optional[Entity]:
32
+ """Resolve a markdown link to a permalink.
33
+
34
+ Args:
35
+ link_text: The link text to resolve
36
+ use_search: Whether to use search-based fuzzy matching as fallback
37
+ strict: If True, only exact matches are allowed (no fuzzy search fallback)
38
+ """
39
+ logger.trace(f"Resolving link: {link_text}")
33
40
 
34
41
  # Clean link text and extract any alias
35
42
  clean_text, alias = self._normalize_link_text(link_text)
@@ -41,24 +48,45 @@ class LinkResolver:
41
48
  return entity
42
49
 
43
50
  # 2. Try exact title match
44
- entity = await self.entity_repository.get_by_title(clean_text)
45
- if entity:
51
+ found = await self.entity_repository.get_by_title(clean_text)
52
+ if found:
53
+ # Return first match if there are duplicates (consistent behavior)
54
+ entity = found[0]
46
55
  logger.debug(f"Found title match: {entity.title}")
47
56
  return entity
48
57
 
58
+ # 3. Try file path
59
+ found_path = await self.entity_repository.get_by_file_path(clean_text)
60
+ if found_path:
61
+ logger.debug(f"Found entity with path: {found_path.file_path}")
62
+ return found_path
63
+
64
+ # 4. Try file path with .md extension if not already present
65
+ if not clean_text.endswith(".md") and "/" in clean_text:
66
+ file_path_with_md = f"{clean_text}.md"
67
+ found_path_md = await self.entity_repository.get_by_file_path(file_path_with_md)
68
+ if found_path_md:
69
+ logger.debug(f"Found entity with path (with .md): {found_path_md.file_path}")
70
+ return found_path_md
71
+
72
+ # In strict mode, don't try fuzzy search - return None if no exact match found
73
+ if strict:
74
+ return None
75
+
76
+ # 5. Fall back to search for fuzzy matching (only if not in strict mode)
49
77
  if use_search and "*" not in clean_text:
50
- # 3. Fall back to search for fuzzy matching on title
51
78
  results = await self.search_service.search(
52
- query=SearchQuery(title=clean_text, types=[SearchItemType.ENTITY]),
79
+ query=SearchQuery(text=clean_text, entity_types=[SearchItemType.ENTITY]),
53
80
  )
54
81
 
55
82
  if results:
56
83
  # Look for best match
57
- best_match = self._select_best_match(clean_text, results)
58
- logger.debug(
84
+ best_match = min(results, key=lambda x: x.score) # pyright: ignore
85
+ logger.trace(
59
86
  f"Selected best match from {len(results)} results: {best_match.permalink}"
60
87
  )
61
- return await self.entity_repository.get_by_permalink(best_match.permalink)
88
+ if best_match.permalink:
89
+ return await self.entity_repository.get_by_permalink(best_match.permalink)
62
90
 
63
91
  # if we couldn't find anything then return None
64
92
  return None
@@ -85,43 +113,8 @@ class LinkResolver:
85
113
  text, alias = text.split("|", 1)
86
114
  text = text.strip()
87
115
  alias = alias.strip()
116
+ else:
117
+ # Strip whitespace from text even if no alias
118
+ text = text.strip()
88
119
 
89
120
  return text, alias
90
-
91
- def _select_best_match(self, search_text: str, results: List[SearchIndexRow]) -> SearchIndexRow:
92
- """Select best match from search results.
93
-
94
- Uses multiple criteria:
95
- 1. Word matches in title field
96
- 2. Word matches in path
97
- 3. Overall search score
98
- """
99
- # Get search terms for matching
100
- terms = search_text.lower().split()
101
-
102
- # Score each result
103
- scored_results = []
104
- for result in results:
105
- # Start with base score (lower is better)
106
- score = result.score
107
- assert score is not None
108
-
109
- # Parse path components
110
- path_parts = result.permalink.lower().split("/")
111
- last_part = path_parts[-1] if path_parts else ""
112
-
113
- # Title word match boosts
114
- term_matches = [term for term in terms if term in last_part]
115
- if term_matches:
116
- score *= 0.5 # Boost for each matching term
117
-
118
- # Exact title match is best
119
- if last_part == search_text.lower():
120
- score *= 0.2
121
-
122
- scored_results.append((score, result))
123
-
124
- # Sort by score (lowest first) and return best
125
- scored_results.sort(key=lambda x: x[0], reverse=True)
126
-
127
- return scored_results[0][1]