basic-memory 0.7.0__py3-none-any.whl → 0.17.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 (195) hide show
  1. basic_memory/__init__.py +5 -1
  2. basic_memory/alembic/alembic.ini +119 -0
  3. basic_memory/alembic/env.py +130 -20
  4. basic_memory/alembic/migrations.py +4 -9
  5. basic_memory/alembic/versions/314f1ea54dc4_add_postgres_full_text_search_support_.py +131 -0
  6. basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py +51 -0
  7. basic_memory/alembic/versions/5fe1ab1ccebe_add_projects_table.py +120 -0
  8. basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py +112 -0
  9. basic_memory/alembic/versions/6830751f5fb6_merge_multiple_heads.py +24 -0
  10. basic_memory/alembic/versions/9d9c1cb7d8f5_add_mtime_and_size_columns_to_entity_.py +49 -0
  11. basic_memory/alembic/versions/a1b2c3d4e5f6_fix_project_foreign_keys.py +49 -0
  12. basic_memory/alembic/versions/a2b3c4d5e6f7_add_search_index_entity_cascade.py +56 -0
  13. basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py +44 -0
  14. basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +113 -0
  15. basic_memory/alembic/versions/e7e1f4367280_add_scan_watermark_tracking_to_project.py +37 -0
  16. basic_memory/alembic/versions/f8a9b2c3d4e5_add_pg_trgm_for_fuzzy_link_resolution.py +239 -0
  17. basic_memory/alembic/versions/g9a0b3c4d5e6_add_external_id_to_project_and_entity.py +173 -0
  18. basic_memory/api/app.py +87 -20
  19. basic_memory/api/container.py +133 -0
  20. basic_memory/api/routers/__init__.py +4 -1
  21. basic_memory/api/routers/directory_router.py +84 -0
  22. basic_memory/api/routers/importer_router.py +152 -0
  23. basic_memory/api/routers/knowledge_router.py +180 -23
  24. basic_memory/api/routers/management_router.py +80 -0
  25. basic_memory/api/routers/memory_router.py +9 -64
  26. basic_memory/api/routers/project_router.py +460 -0
  27. basic_memory/api/routers/prompt_router.py +260 -0
  28. basic_memory/api/routers/resource_router.py +136 -11
  29. basic_memory/api/routers/search_router.py +5 -5
  30. basic_memory/api/routers/utils.py +169 -0
  31. basic_memory/api/template_loader.py +292 -0
  32. basic_memory/api/v2/__init__.py +35 -0
  33. basic_memory/api/v2/routers/__init__.py +21 -0
  34. basic_memory/api/v2/routers/directory_router.py +93 -0
  35. basic_memory/api/v2/routers/importer_router.py +181 -0
  36. basic_memory/api/v2/routers/knowledge_router.py +427 -0
  37. basic_memory/api/v2/routers/memory_router.py +130 -0
  38. basic_memory/api/v2/routers/project_router.py +359 -0
  39. basic_memory/api/v2/routers/prompt_router.py +269 -0
  40. basic_memory/api/v2/routers/resource_router.py +286 -0
  41. basic_memory/api/v2/routers/search_router.py +73 -0
  42. basic_memory/cli/app.py +80 -10
  43. basic_memory/cli/auth.py +300 -0
  44. basic_memory/cli/commands/__init__.py +15 -2
  45. basic_memory/cli/commands/cloud/__init__.py +6 -0
  46. basic_memory/cli/commands/cloud/api_client.py +127 -0
  47. basic_memory/cli/commands/cloud/bisync_commands.py +110 -0
  48. basic_memory/cli/commands/cloud/cloud_utils.py +108 -0
  49. basic_memory/cli/commands/cloud/core_commands.py +195 -0
  50. basic_memory/cli/commands/cloud/rclone_commands.py +397 -0
  51. basic_memory/cli/commands/cloud/rclone_config.py +110 -0
  52. basic_memory/cli/commands/cloud/rclone_installer.py +263 -0
  53. basic_memory/cli/commands/cloud/upload.py +240 -0
  54. basic_memory/cli/commands/cloud/upload_command.py +124 -0
  55. basic_memory/cli/commands/command_utils.py +99 -0
  56. basic_memory/cli/commands/db.py +87 -12
  57. basic_memory/cli/commands/format.py +198 -0
  58. basic_memory/cli/commands/import_chatgpt.py +47 -223
  59. basic_memory/cli/commands/import_claude_conversations.py +48 -171
  60. basic_memory/cli/commands/import_claude_projects.py +53 -160
  61. basic_memory/cli/commands/import_memory_json.py +55 -111
  62. basic_memory/cli/commands/mcp.py +67 -11
  63. basic_memory/cli/commands/project.py +889 -0
  64. basic_memory/cli/commands/status.py +52 -34
  65. basic_memory/cli/commands/telemetry.py +81 -0
  66. basic_memory/cli/commands/tool.py +341 -0
  67. basic_memory/cli/container.py +84 -0
  68. basic_memory/cli/main.py +14 -6
  69. basic_memory/config.py +580 -26
  70. basic_memory/db.py +285 -28
  71. basic_memory/deps/__init__.py +293 -0
  72. basic_memory/deps/config.py +26 -0
  73. basic_memory/deps/db.py +56 -0
  74. basic_memory/deps/importers.py +200 -0
  75. basic_memory/deps/projects.py +238 -0
  76. basic_memory/deps/repositories.py +179 -0
  77. basic_memory/deps/services.py +480 -0
  78. basic_memory/deps.py +16 -185
  79. basic_memory/file_utils.py +318 -54
  80. basic_memory/ignore_utils.py +297 -0
  81. basic_memory/importers/__init__.py +27 -0
  82. basic_memory/importers/base.py +100 -0
  83. basic_memory/importers/chatgpt_importer.py +245 -0
  84. basic_memory/importers/claude_conversations_importer.py +192 -0
  85. basic_memory/importers/claude_projects_importer.py +184 -0
  86. basic_memory/importers/memory_json_importer.py +128 -0
  87. basic_memory/importers/utils.py +61 -0
  88. basic_memory/markdown/entity_parser.py +182 -23
  89. basic_memory/markdown/markdown_processor.py +70 -7
  90. basic_memory/markdown/plugins.py +43 -23
  91. basic_memory/markdown/schemas.py +1 -1
  92. basic_memory/markdown/utils.py +38 -14
  93. basic_memory/mcp/async_client.py +135 -4
  94. basic_memory/mcp/clients/__init__.py +28 -0
  95. basic_memory/mcp/clients/directory.py +70 -0
  96. basic_memory/mcp/clients/knowledge.py +176 -0
  97. basic_memory/mcp/clients/memory.py +120 -0
  98. basic_memory/mcp/clients/project.py +89 -0
  99. basic_memory/mcp/clients/resource.py +71 -0
  100. basic_memory/mcp/clients/search.py +65 -0
  101. basic_memory/mcp/container.py +110 -0
  102. basic_memory/mcp/project_context.py +155 -0
  103. basic_memory/mcp/prompts/__init__.py +19 -0
  104. basic_memory/mcp/prompts/ai_assistant_guide.py +70 -0
  105. basic_memory/mcp/prompts/continue_conversation.py +62 -0
  106. basic_memory/mcp/prompts/recent_activity.py +188 -0
  107. basic_memory/mcp/prompts/search.py +57 -0
  108. basic_memory/mcp/prompts/utils.py +162 -0
  109. basic_memory/mcp/resources/ai_assistant_guide.md +283 -0
  110. basic_memory/mcp/resources/project_info.py +71 -0
  111. basic_memory/mcp/server.py +61 -9
  112. basic_memory/mcp/tools/__init__.py +33 -21
  113. basic_memory/mcp/tools/build_context.py +120 -0
  114. basic_memory/mcp/tools/canvas.py +152 -0
  115. basic_memory/mcp/tools/chatgpt_tools.py +190 -0
  116. basic_memory/mcp/tools/delete_note.py +249 -0
  117. basic_memory/mcp/tools/edit_note.py +325 -0
  118. basic_memory/mcp/tools/list_directory.py +157 -0
  119. basic_memory/mcp/tools/move_note.py +549 -0
  120. basic_memory/mcp/tools/project_management.py +204 -0
  121. basic_memory/mcp/tools/read_content.py +281 -0
  122. basic_memory/mcp/tools/read_note.py +265 -0
  123. basic_memory/mcp/tools/recent_activity.py +528 -0
  124. basic_memory/mcp/tools/search.py +377 -24
  125. basic_memory/mcp/tools/utils.py +402 -16
  126. basic_memory/mcp/tools/view_note.py +78 -0
  127. basic_memory/mcp/tools/write_note.py +230 -0
  128. basic_memory/models/__init__.py +3 -2
  129. basic_memory/models/knowledge.py +82 -17
  130. basic_memory/models/project.py +93 -0
  131. basic_memory/models/search.py +68 -8
  132. basic_memory/project_resolver.py +222 -0
  133. basic_memory/repository/__init__.py +2 -0
  134. basic_memory/repository/entity_repository.py +437 -8
  135. basic_memory/repository/observation_repository.py +36 -3
  136. basic_memory/repository/postgres_search_repository.py +451 -0
  137. basic_memory/repository/project_info_repository.py +10 -0
  138. basic_memory/repository/project_repository.py +140 -0
  139. basic_memory/repository/relation_repository.py +79 -4
  140. basic_memory/repository/repository.py +148 -29
  141. basic_memory/repository/search_index_row.py +95 -0
  142. basic_memory/repository/search_repository.py +79 -268
  143. basic_memory/repository/search_repository_base.py +241 -0
  144. basic_memory/repository/sqlite_search_repository.py +437 -0
  145. basic_memory/runtime.py +61 -0
  146. basic_memory/schemas/__init__.py +22 -9
  147. basic_memory/schemas/base.py +131 -12
  148. basic_memory/schemas/cloud.py +50 -0
  149. basic_memory/schemas/directory.py +31 -0
  150. basic_memory/schemas/importer.py +35 -0
  151. basic_memory/schemas/memory.py +194 -25
  152. basic_memory/schemas/project_info.py +213 -0
  153. basic_memory/schemas/prompt.py +90 -0
  154. basic_memory/schemas/request.py +56 -2
  155. basic_memory/schemas/response.py +85 -28
  156. basic_memory/schemas/search.py +36 -35
  157. basic_memory/schemas/sync_report.py +72 -0
  158. basic_memory/schemas/v2/__init__.py +27 -0
  159. basic_memory/schemas/v2/entity.py +133 -0
  160. basic_memory/schemas/v2/resource.py +47 -0
  161. basic_memory/services/__init__.py +2 -1
  162. basic_memory/services/context_service.py +451 -138
  163. basic_memory/services/directory_service.py +310 -0
  164. basic_memory/services/entity_service.py +636 -71
  165. basic_memory/services/exceptions.py +21 -0
  166. basic_memory/services/file_service.py +402 -33
  167. basic_memory/services/initialization.py +216 -0
  168. basic_memory/services/link_resolver.py +50 -56
  169. basic_memory/services/project_service.py +888 -0
  170. basic_memory/services/search_service.py +232 -37
  171. basic_memory/sync/__init__.py +4 -2
  172. basic_memory/sync/background_sync.py +26 -0
  173. basic_memory/sync/coordinator.py +160 -0
  174. basic_memory/sync/sync_service.py +1200 -109
  175. basic_memory/sync/watch_service.py +432 -135
  176. basic_memory/telemetry.py +249 -0
  177. basic_memory/templates/prompts/continue_conversation.hbs +110 -0
  178. basic_memory/templates/prompts/search.hbs +101 -0
  179. basic_memory/utils.py +407 -54
  180. basic_memory-0.17.4.dist-info/METADATA +617 -0
  181. basic_memory-0.17.4.dist-info/RECORD +193 -0
  182. {basic_memory-0.7.0.dist-info → basic_memory-0.17.4.dist-info}/WHEEL +1 -1
  183. {basic_memory-0.7.0.dist-info → basic_memory-0.17.4.dist-info}/entry_points.txt +1 -0
  184. basic_memory/alembic/README +0 -1
  185. basic_memory/cli/commands/sync.py +0 -206
  186. basic_memory/cli/commands/tools.py +0 -157
  187. basic_memory/mcp/tools/knowledge.py +0 -68
  188. basic_memory/mcp/tools/memory.py +0 -170
  189. basic_memory/mcp/tools/notes.py +0 -202
  190. basic_memory/schemas/discovery.py +0 -28
  191. basic_memory/sync/file_change_scanner.py +0 -158
  192. basic_memory/sync/utils.py +0 -31
  193. basic_memory-0.7.0.dist-info/METADATA +0 -378
  194. basic_memory-0.7.0.dist-info/RECORD +0 -82
  195. {basic_memory-0.7.0.dist-info → basic_memory-0.17.4.dist-info}/licenses/LICENSE +0 -0
@@ -3,144 +3,29 @@
3
3
  import asyncio
4
4
  import json
5
5
  from pathlib import Path
6
- from typing import Dict, Any, Annotated, Optional
6
+ from typing import Annotated, Tuple
7
7
 
8
- import logfire
9
8
  import typer
9
+ from basic_memory.cli.app import claude_app
10
+ from basic_memory.config import ConfigManager, get_project_config
11
+ from basic_memory.importers.claude_projects_importer import ClaudeProjectsImporter
12
+ from basic_memory.markdown import EntityParser, MarkdownProcessor
13
+ from basic_memory.services.file_service import FileService
10
14
  from loguru import logger
11
15
  from rich.console import Console
12
16
  from rich.panel import Panel
13
- from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
14
-
15
- from basic_memory.cli.app import claude_app
16
- from basic_memory.config import config
17
- from basic_memory.markdown import EntityParser, MarkdownProcessor
18
- from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter
19
17
 
20
18
  console = Console()
21
19
 
22
20
 
23
- def clean_filename(text: str) -> str:
24
- """Convert text to safe filename."""
25
- clean = "".join(c if c.isalnum() else "-" for c in text.lower()).strip("-")
26
- return clean
27
-
28
-
29
- def format_project_markdown(project: Dict[str, Any], doc: Dict[str, Any]) -> EntityMarkdown:
30
- """Format a project document as a Basic Memory entity."""
31
-
32
- # Extract timestamps
33
- created_at = doc.get("created_at") or project["created_at"]
34
- modified_at = project["updated_at"]
35
-
36
- # Generate clean names for organization
37
- project_dir = clean_filename(project["name"])
38
- doc_file = clean_filename(doc["filename"])
39
-
40
- # Create entity
41
- entity = EntityMarkdown(
42
- frontmatter=EntityFrontmatter(
43
- metadata={
44
- "type": "project_doc",
45
- "title": doc["filename"],
46
- "created": created_at,
47
- "modified": modified_at,
48
- "permalink": f"{project_dir}/docs/{doc_file}",
49
- "project_name": project["name"],
50
- "project_uuid": project["uuid"],
51
- "doc_uuid": doc["uuid"],
52
- }
53
- ),
54
- content=doc["content"],
55
- )
56
-
57
- return entity
58
-
59
-
60
- def format_prompt_markdown(project: Dict[str, Any]) -> Optional[EntityMarkdown]:
61
- """Format project prompt template as a Basic Memory entity."""
62
-
63
- if not project.get("prompt_template"):
64
- return None
65
-
66
- # Extract timestamps
67
- created_at = project["created_at"]
68
- modified_at = project["updated_at"]
69
-
70
- # Generate clean project directory name
71
- project_dir = clean_filename(project["name"])
72
-
73
- # Create entity
74
- entity = EntityMarkdown(
75
- frontmatter=EntityFrontmatter(
76
- metadata={
77
- "type": "prompt_template",
78
- "title": f"Prompt Template: {project['name']}",
79
- "created": created_at,
80
- "modified": modified_at,
81
- "permalink": f"{project_dir}/prompt-template",
82
- "project_name": project["name"],
83
- "project_uuid": project["uuid"],
84
- }
85
- ),
86
- content=f"# Prompt Template: {project['name']}\n\n{project['prompt_template']}",
87
- )
88
-
89
- return entity
90
-
91
-
92
- async def process_projects_json(
93
- json_path: Path, base_path: Path, markdown_processor: MarkdownProcessor
94
- ) -> Dict[str, int]:
95
- """Import project data from Claude.ai projects.json format."""
96
-
97
- with Progress(
98
- SpinnerColumn(),
99
- TextColumn("[progress.description]{task.description}"),
100
- BarColumn(),
101
- TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
102
- console=console,
103
- ) as progress:
104
- read_task = progress.add_task("Reading project data...", total=None)
105
-
106
- # Read project data
107
- data = json.loads(json_path.read_text())
108
- progress.update(read_task, total=len(data))
109
-
110
- # Track import counts
111
- docs_imported = 0
112
- prompts_imported = 0
113
-
114
- # Process each project
115
- for project in data:
116
- project_dir = clean_filename(project["name"])
117
-
118
- # Create project directories
119
- docs_dir = base_path / project_dir / "docs"
120
- docs_dir.mkdir(parents=True, exist_ok=True)
121
-
122
- # Import prompt template if it exists
123
- if prompt_entity := format_prompt_markdown(project):
124
- file_path = base_path / f"{prompt_entity.frontmatter.metadata['permalink']}.md"
125
- await markdown_processor.write_file(file_path, prompt_entity)
126
- prompts_imported += 1
127
-
128
- # Import project documents
129
- for doc in project.get("docs", []):
130
- entity = format_project_markdown(project, doc)
131
- file_path = base_path / f"{entity.frontmatter.metadata['permalink']}.md"
132
- await markdown_processor.write_file(file_path, entity)
133
- docs_imported += 1
134
-
135
- progress.update(read_task, advance=1)
136
-
137
- return {"documents": docs_imported, "prompts": prompts_imported}
138
-
139
-
140
- async def get_markdown_processor() -> MarkdownProcessor:
141
- """Get MarkdownProcessor instance."""
21
+ async def get_importer_dependencies() -> Tuple[MarkdownProcessor, FileService]:
22
+ """Get MarkdownProcessor and FileService instances for importers."""
23
+ config = get_project_config()
24
+ app_config = ConfigManager().config
142
25
  entity_parser = EntityParser(config.home)
143
- return MarkdownProcessor(entity_parser)
26
+ markdown_processor = MarkdownProcessor(entity_parser, app_config=app_config)
27
+ file_service = FileService(config.home, markdown_processor, app_config=app_config)
28
+ return markdown_processor, file_service
144
29
 
145
30
 
146
31
  @claude_app.command(name="projects", help="Import projects from Claude.ai.")
@@ -161,36 +46,44 @@ def import_projects(
161
46
 
162
47
  After importing, run 'basic-memory sync' to index the new files.
163
48
  """
164
- with logfire.span("import claude projects"): # pyright: ignore [reportGeneralTypeIssues]
165
- try:
166
- if projects_json:
167
- if not projects_json.exists():
168
- typer.echo(f"Error: File not found: {projects_json}", err=True)
169
- raise typer.Exit(1)
170
-
171
- # Get markdown processor
172
- markdown_processor = asyncio.run(get_markdown_processor())
173
-
174
- # Process the file
175
- base_path = config.home / base_folder if base_folder else config.home
176
- console.print(f"\nImporting projects from {projects_json}...writing to {base_path}")
177
- results = asyncio.run(
178
- process_projects_json(projects_json, base_path, markdown_processor)
179
- )
180
-
181
- # Show results
182
- console.print(
183
- Panel(
184
- f"[green]Import complete![/green]\n\n"
185
- f"Imported {results['documents']} project documents\n"
186
- f"Imported {results['prompts']} prompt templates",
187
- expand=False,
188
- )
189
- )
190
-
191
- console.print("\nRun 'basic-memory sync' to index the new files.")
192
-
193
- except Exception as e:
194
- logger.error("Import failed")
195
- typer.echo(f"Error during import: {e}", err=True)
49
+ config = get_project_config()
50
+ try:
51
+ if not projects_json.exists():
52
+ typer.echo(f"Error: File not found: {projects_json}", err=True)
53
+ raise typer.Exit(1)
54
+
55
+ # Get importer dependencies
56
+ markdown_processor, file_service = asyncio.run(get_importer_dependencies())
57
+
58
+ # Create the importer
59
+ importer = ClaudeProjectsImporter(config.home, markdown_processor, file_service)
60
+
61
+ # Process the file
62
+ base_path = config.home / base_folder if base_folder else config.home
63
+ console.print(f"\nImporting projects from {projects_json}...writing to {base_path}")
64
+
65
+ # Run the import
66
+ with projects_json.open("r", encoding="utf-8") as file:
67
+ json_data = json.load(file)
68
+ result = asyncio.run(importer.import_data(json_data, base_folder))
69
+
70
+ if not result.success: # pragma: no cover
71
+ typer.echo(f"Error during import: {result.error_message}", err=True)
196
72
  raise typer.Exit(1)
73
+
74
+ # Show results
75
+ console.print(
76
+ Panel(
77
+ f"[green]Import complete![/green]\n\n"
78
+ f"Imported {result.documents} project documents\n"
79
+ f"Imported {result.prompts} prompt templates",
80
+ expand=False,
81
+ )
82
+ )
83
+
84
+ console.print("\nRun 'basic-memory sync' to index the new files.")
85
+
86
+ except Exception as e:
87
+ logger.error("Import failed")
88
+ typer.echo(f"Error during import: {e}", err=True)
89
+ raise typer.Exit(1)
@@ -3,99 +3,29 @@
3
3
  import asyncio
4
4
  import json
5
5
  from pathlib import Path
6
- from typing import Dict, Any, List, Annotated
6
+ from typing import Annotated, Tuple
7
7
 
8
- import logfire
9
8
  import typer
9
+ from basic_memory.cli.app import import_app
10
+ from basic_memory.config import ConfigManager, get_project_config
11
+ from basic_memory.importers.memory_json_importer import MemoryJsonImporter
12
+ from basic_memory.markdown import EntityParser, MarkdownProcessor
13
+ from basic_memory.services.file_service import FileService
10
14
  from loguru import logger
11
15
  from rich.console import Console
12
16
  from rich.panel import Panel
13
- from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
14
-
15
- from basic_memory.cli.app import import_app
16
- from basic_memory.config import config
17
- from basic_memory.markdown import EntityParser, MarkdownProcessor
18
- from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter, Observation, Relation
19
17
 
20
18
  console = Console()
21
19
 
22
20
 
23
- async def process_memory_json(
24
- json_path: Path, base_path: Path, markdown_processor: MarkdownProcessor
25
- ):
26
- """Import entities from memory.json using markdown processor."""
27
-
28
- # First pass - collect all relations by source entity
29
- entity_relations: Dict[str, List[Relation]] = {}
30
- entities: Dict[str, Dict[str, Any]] = {}
31
-
32
- with Progress(
33
- SpinnerColumn(),
34
- TextColumn("[progress.description]{task.description}"),
35
- BarColumn(),
36
- TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
37
- console=console,
38
- ) as progress:
39
- read_task = progress.add_task("Reading memory.json...", total=None)
40
-
41
- # First pass - collect entities and relations
42
- with open(json_path) as f:
43
- lines = f.readlines()
44
- progress.update(read_task, total=len(lines))
45
-
46
- for line in lines:
47
- data = json.loads(line)
48
- if data["type"] == "entity":
49
- entities[data["name"]] = data
50
- elif data["type"] == "relation":
51
- # Store relation with its source entity
52
- source = data.get("from") or data.get("from_id")
53
- if source not in entity_relations:
54
- entity_relations[source] = []
55
- entity_relations[source].append(
56
- Relation(
57
- type=data.get("relationType") or data.get("relation_type"),
58
- target=data.get("to") or data.get("to_id"),
59
- )
60
- )
61
- progress.update(read_task, advance=1)
62
-
63
- # Second pass - create and write entities
64
- write_task = progress.add_task("Creating entities...", total=len(entities))
65
-
66
- entities_created = 0
67
- for name, entity_data in entities.items():
68
- entity = EntityMarkdown(
69
- frontmatter=EntityFrontmatter(
70
- metadata={
71
- "type": entity_data["entityType"],
72
- "title": name,
73
- "permalink": f"{entity_data['entityType']}/{name}",
74
- }
75
- ),
76
- content=f"# {name}\n",
77
- observations=[Observation(content=obs) for obs in entity_data["observations"]],
78
- relations=entity_relations.get(
79
- name, []
80
- ), # Add any relations where this entity is the source
81
- )
82
-
83
- # Let markdown processor handle writing
84
- file_path = base_path / f"{entity_data['entityType']}/{name}.md"
85
- await markdown_processor.write_file(file_path, entity)
86
- entities_created += 1
87
- progress.update(write_task, advance=1)
88
-
89
- return {
90
- "entities": entities_created,
91
- "relations": sum(len(rels) for rels in entity_relations.values()),
92
- }
93
-
94
-
95
- async def get_markdown_processor() -> MarkdownProcessor:
96
- """Get MarkdownProcessor instance."""
21
+ async def get_importer_dependencies() -> Tuple[MarkdownProcessor, FileService]:
22
+ """Get MarkdownProcessor and FileService instances for importers."""
23
+ config = get_project_config()
24
+ app_config = ConfigManager().config
97
25
  entity_parser = EntityParser(config.home)
98
- return MarkdownProcessor(entity_parser)
26
+ markdown_processor = MarkdownProcessor(entity_parser, app_config=app_config)
27
+ file_service = FileService(config.home, markdown_processor, app_config=app_config)
28
+ return markdown_processor, file_service
99
29
 
100
30
 
101
31
  @import_app.command()
@@ -103,6 +33,9 @@ def memory_json(
103
33
  json_path: Annotated[Path, typer.Argument(..., help="Path to memory.json file")] = Path(
104
34
  "memory.json"
105
35
  ),
36
+ destination_folder: Annotated[
37
+ str, typer.Option(help="Optional destination folder within the project")
38
+ ] = "",
106
39
  ):
107
40
  """Import entities and relations from a memory.json file.
108
41
 
@@ -110,37 +43,48 @@ def memory_json(
110
43
  1. Read entities and relations from the JSON file
111
44
  2. Create markdown files for each entity
112
45
  3. Include outgoing relations in each entity's markdown
113
-
114
- After importing, run 'basic-memory sync' to index the new files.
115
46
  """
116
47
 
117
- with logfire.span("import memory_json"): # pyright: ignore [reportGeneralTypeIssues]
118
- if not json_path.exists():
119
- typer.echo(f"Error: File not found: {json_path}", err=True)
120
- raise typer.Exit(1)
48
+ if not json_path.exists():
49
+ typer.echo(f"Error: File not found: {json_path}", err=True)
50
+ raise typer.Exit(1)
121
51
 
122
- try:
123
- # Get markdown processor
124
- markdown_processor = asyncio.run(get_markdown_processor())
125
-
126
- # Process the file
127
- base_path = config.home
128
- console.print(f"\nImporting from {json_path}...writing to {base_path}")
129
- results = asyncio.run(process_memory_json(json_path, base_path, markdown_processor))
130
-
131
- # Show results
132
- console.print(
133
- Panel(
134
- f"[green]Import complete![/green]\n\n"
135
- f"Created {results['entities']} entities\n"
136
- f"Added {results['relations']} relations",
137
- expand=False,
138
- )
139
- )
52
+ config = get_project_config()
53
+ try:
54
+ # Get importer dependencies
55
+ markdown_processor, file_service = asyncio.run(get_importer_dependencies())
56
+
57
+ # Create the importer
58
+ importer = MemoryJsonImporter(config.home, markdown_processor, file_service)
140
59
 
141
- console.print("\nRun 'basic-memory sync' to index the new files.")
60
+ # Process the file
61
+ base_path = config.home if not destination_folder else config.home / destination_folder
62
+ console.print(f"\nImporting from {json_path}...writing to {base_path}")
142
63
 
143
- except Exception as e:
144
- logger.error("Import failed")
145
- typer.echo(f"Error during import: {e}", err=True)
64
+ # Run the import for json log format
65
+ file_data = []
66
+ with json_path.open("r", encoding="utf-8") as file:
67
+ for line in file:
68
+ json_data = json.loads(line)
69
+ file_data.append(json_data)
70
+ result = asyncio.run(importer.import_data(file_data, destination_folder))
71
+
72
+ if not result.success: # pragma: no cover
73
+ typer.echo(f"Error during import: {result.error_message}", err=True)
146
74
  raise typer.Exit(1)
75
+
76
+ # Show results
77
+ console.print(
78
+ Panel(
79
+ f"[green]Import complete![/green]\n\n"
80
+ f"Created {result.entities} entities\n"
81
+ f"Added {result.relations} relations\n"
82
+ f"Skipped {result.skipped_entities} entities\n",
83
+ expand=False,
84
+ )
85
+ )
86
+
87
+ except Exception as e:
88
+ logger.error("Import failed")
89
+ typer.echo(f"Error during import: {e}", err=True)
90
+ raise typer.Exit(1)
@@ -1,20 +1,76 @@
1
- """MCP server command."""
1
+ """MCP server command with streamable HTTP transport."""
2
+
3
+ import os
4
+ import typer
5
+ from typing import Optional
2
6
 
3
- from loguru import logger
4
7
  from basic_memory.cli.app import app
5
- from basic_memory.config import config
8
+ from basic_memory.config import ConfigManager, init_mcp_logging
6
9
 
7
- # Import mcp instance
10
+ # Import mcp instance (has lifespan that handles initialization and file sync)
8
11
  from basic_memory.mcp.server import mcp as mcp_server # pragma: no cover
9
12
 
10
13
  # Import mcp tools to register them
11
14
  import basic_memory.mcp.tools # noqa: F401 # pragma: no cover
12
15
 
16
+ # Import prompts to register them
17
+ import basic_memory.mcp.prompts # noqa: F401 # pragma: no cover
18
+ from loguru import logger
19
+
20
+ config = ConfigManager().config
21
+
22
+ if not config.cloud_mode_enabled:
23
+
24
+ @app.command()
25
+ def mcp(
26
+ transport: str = typer.Option(
27
+ "stdio", help="Transport type: stdio, streamable-http, or sse"
28
+ ),
29
+ host: str = typer.Option(
30
+ "0.0.0.0", help="Host for HTTP transports (use 0.0.0.0 to allow external connections)"
31
+ ),
32
+ port: int = typer.Option(8000, help="Port for HTTP transports"),
33
+ path: str = typer.Option("/mcp", help="Path prefix for streamable-http transport"),
34
+ project: Optional[str] = typer.Option(None, help="Restrict MCP server to single project"),
35
+ ): # pragma: no cover
36
+ """Run the MCP server with configurable transport options.
37
+
38
+ This command starts an MCP server using one of three transport options:
39
+
40
+ - stdio: Standard I/O (good for local usage)
41
+ - streamable-http: Recommended for web deployments (default)
42
+ - sse: Server-Sent Events (for compatibility with existing clients)
43
+
44
+ Initialization, file sync, and cleanup are handled by the MCP server's lifespan.
45
+ """
46
+ # Initialize logging for MCP (file only, stdout breaks protocol)
47
+ init_mcp_logging()
48
+
49
+ # Validate and set project constraint if specified
50
+ if project:
51
+ config_manager = ConfigManager()
52
+ project_name, _ = config_manager.get_project(project)
53
+ if not project_name:
54
+ typer.echo(f"No project found named: {project}", err=True)
55
+ raise typer.Exit(1)
56
+
57
+ # Set env var with validated project name
58
+ os.environ["BASIC_MEMORY_MCP_PROJECT"] = project_name
59
+ logger.info(f"MCP server constrained to project: {project_name}")
60
+
61
+ # Run the MCP server (blocks)
62
+ # Lifespan handles: initialization, migrations, file sync, cleanup
63
+ logger.info(f"Starting MCP server with {transport.upper()} transport")
13
64
 
14
- @app.command()
15
- def mcp(): # pragma: no cover
16
- """Run the MCP server for Claude Desktop integration."""
17
- home_dir = config.home
18
- logger.info(f"Starting Basic Memory MCP server {basic_memory.__version__}")
19
- logger.info(f"Home directory: {home_dir}")
20
- mcp_server.run()
65
+ if transport == "stdio":
66
+ mcp_server.run(
67
+ transport=transport,
68
+ )
69
+ elif transport == "streamable-http" or transport == "sse":
70
+ mcp_server.run(
71
+ transport=transport,
72
+ host=host,
73
+ port=port,
74
+ path=path,
75
+ log_level="INFO",
76
+ )