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.
- basic_memory/__init__.py +5 -1
- basic_memory/alembic/alembic.ini +119 -0
- basic_memory/alembic/env.py +27 -3
- basic_memory/alembic/migrations.py +4 -9
- basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py +51 -0
- basic_memory/alembic/versions/5fe1ab1ccebe_add_projects_table.py +108 -0
- basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py +104 -0
- basic_memory/alembic/versions/9d9c1cb7d8f5_add_mtime_and_size_columns_to_entity_.py +49 -0
- basic_memory/alembic/versions/a1b2c3d4e5f6_fix_project_foreign_keys.py +49 -0
- basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py +44 -0
- basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +100 -0
- basic_memory/alembic/versions/e7e1f4367280_add_scan_watermark_tracking_to_project.py +37 -0
- basic_memory/api/app.py +64 -18
- basic_memory/api/routers/__init__.py +4 -1
- basic_memory/api/routers/directory_router.py +84 -0
- basic_memory/api/routers/importer_router.py +152 -0
- basic_memory/api/routers/knowledge_router.py +166 -21
- basic_memory/api/routers/management_router.py +80 -0
- basic_memory/api/routers/memory_router.py +9 -64
- basic_memory/api/routers/project_router.py +406 -0
- basic_memory/api/routers/prompt_router.py +260 -0
- basic_memory/api/routers/resource_router.py +119 -4
- basic_memory/api/routers/search_router.py +5 -5
- basic_memory/api/routers/utils.py +130 -0
- basic_memory/api/template_loader.py +292 -0
- basic_memory/cli/app.py +43 -9
- basic_memory/cli/auth.py +277 -0
- basic_memory/cli/commands/__init__.py +13 -2
- basic_memory/cli/commands/cloud/__init__.py +6 -0
- basic_memory/cli/commands/cloud/api_client.py +112 -0
- basic_memory/cli/commands/cloud/bisync_commands.py +110 -0
- basic_memory/cli/commands/cloud/cloud_utils.py +101 -0
- basic_memory/cli/commands/cloud/core_commands.py +195 -0
- basic_memory/cli/commands/cloud/rclone_commands.py +301 -0
- basic_memory/cli/commands/cloud/rclone_config.py +110 -0
- basic_memory/cli/commands/cloud/rclone_installer.py +249 -0
- basic_memory/cli/commands/cloud/upload.py +233 -0
- basic_memory/cli/commands/cloud/upload_command.py +124 -0
- basic_memory/cli/commands/command_utils.py +51 -0
- basic_memory/cli/commands/db.py +28 -12
- basic_memory/cli/commands/import_chatgpt.py +40 -220
- basic_memory/cli/commands/import_claude_conversations.py +41 -168
- basic_memory/cli/commands/import_claude_projects.py +46 -157
- basic_memory/cli/commands/import_memory_json.py +48 -108
- basic_memory/cli/commands/mcp.py +84 -10
- basic_memory/cli/commands/project.py +876 -0
- basic_memory/cli/commands/status.py +50 -33
- basic_memory/cli/commands/tool.py +341 -0
- basic_memory/cli/main.py +8 -7
- basic_memory/config.py +477 -23
- basic_memory/db.py +168 -17
- basic_memory/deps.py +251 -25
- basic_memory/file_utils.py +113 -58
- basic_memory/ignore_utils.py +297 -0
- basic_memory/importers/__init__.py +27 -0
- basic_memory/importers/base.py +79 -0
- basic_memory/importers/chatgpt_importer.py +232 -0
- basic_memory/importers/claude_conversations_importer.py +177 -0
- basic_memory/importers/claude_projects_importer.py +148 -0
- basic_memory/importers/memory_json_importer.py +108 -0
- basic_memory/importers/utils.py +58 -0
- basic_memory/markdown/entity_parser.py +143 -23
- basic_memory/markdown/markdown_processor.py +3 -3
- basic_memory/markdown/plugins.py +39 -21
- basic_memory/markdown/schemas.py +1 -1
- basic_memory/markdown/utils.py +28 -13
- basic_memory/mcp/async_client.py +134 -4
- basic_memory/mcp/project_context.py +141 -0
- basic_memory/mcp/prompts/__init__.py +19 -0
- basic_memory/mcp/prompts/ai_assistant_guide.py +70 -0
- basic_memory/mcp/prompts/continue_conversation.py +62 -0
- basic_memory/mcp/prompts/recent_activity.py +188 -0
- basic_memory/mcp/prompts/search.py +57 -0
- basic_memory/mcp/prompts/utils.py +162 -0
- basic_memory/mcp/resources/ai_assistant_guide.md +283 -0
- basic_memory/mcp/resources/project_info.py +71 -0
- basic_memory/mcp/server.py +7 -13
- basic_memory/mcp/tools/__init__.py +33 -21
- basic_memory/mcp/tools/build_context.py +120 -0
- basic_memory/mcp/tools/canvas.py +130 -0
- basic_memory/mcp/tools/chatgpt_tools.py +187 -0
- basic_memory/mcp/tools/delete_note.py +225 -0
- basic_memory/mcp/tools/edit_note.py +320 -0
- basic_memory/mcp/tools/list_directory.py +167 -0
- basic_memory/mcp/tools/move_note.py +545 -0
- basic_memory/mcp/tools/project_management.py +200 -0
- basic_memory/mcp/tools/read_content.py +271 -0
- basic_memory/mcp/tools/read_note.py +255 -0
- basic_memory/mcp/tools/recent_activity.py +534 -0
- basic_memory/mcp/tools/search.py +369 -23
- basic_memory/mcp/tools/utils.py +374 -16
- basic_memory/mcp/tools/view_note.py +77 -0
- basic_memory/mcp/tools/write_note.py +207 -0
- basic_memory/models/__init__.py +3 -2
- basic_memory/models/knowledge.py +67 -15
- basic_memory/models/project.py +87 -0
- basic_memory/models/search.py +10 -6
- basic_memory/repository/__init__.py +2 -0
- basic_memory/repository/entity_repository.py +229 -7
- basic_memory/repository/observation_repository.py +35 -3
- basic_memory/repository/project_info_repository.py +10 -0
- basic_memory/repository/project_repository.py +103 -0
- basic_memory/repository/relation_repository.py +21 -2
- basic_memory/repository/repository.py +147 -29
- basic_memory/repository/search_repository.py +411 -62
- basic_memory/schemas/__init__.py +22 -9
- basic_memory/schemas/base.py +97 -8
- basic_memory/schemas/cloud.py +50 -0
- basic_memory/schemas/directory.py +30 -0
- basic_memory/schemas/importer.py +35 -0
- basic_memory/schemas/memory.py +187 -25
- basic_memory/schemas/project_info.py +211 -0
- basic_memory/schemas/prompt.py +90 -0
- basic_memory/schemas/request.py +56 -2
- basic_memory/schemas/response.py +1 -1
- basic_memory/schemas/search.py +31 -35
- basic_memory/schemas/sync_report.py +72 -0
- basic_memory/services/__init__.py +2 -1
- basic_memory/services/context_service.py +241 -104
- basic_memory/services/directory_service.py +295 -0
- basic_memory/services/entity_service.py +590 -60
- basic_memory/services/exceptions.py +21 -0
- basic_memory/services/file_service.py +284 -30
- basic_memory/services/initialization.py +191 -0
- basic_memory/services/link_resolver.py +49 -56
- basic_memory/services/project_service.py +863 -0
- basic_memory/services/search_service.py +168 -32
- basic_memory/sync/__init__.py +3 -2
- basic_memory/sync/background_sync.py +26 -0
- basic_memory/sync/sync_service.py +1180 -109
- basic_memory/sync/watch_service.py +412 -135
- basic_memory/templates/prompts/continue_conversation.hbs +110 -0
- basic_memory/templates/prompts/search.hbs +101 -0
- basic_memory/utils.py +383 -51
- basic_memory-0.16.1.dist-info/METADATA +493 -0
- basic_memory-0.16.1.dist-info/RECORD +148 -0
- {basic_memory-0.7.0.dist-info → basic_memory-0.16.1.dist-info}/entry_points.txt +1 -0
- basic_memory/alembic/README +0 -1
- basic_memory/cli/commands/sync.py +0 -206
- basic_memory/cli/commands/tools.py +0 -157
- basic_memory/mcp/tools/knowledge.py +0 -68
- basic_memory/mcp/tools/memory.py +0 -170
- basic_memory/mcp/tools/notes.py +0 -202
- basic_memory/schemas/discovery.py +0 -28
- basic_memory/sync/file_change_scanner.py +0 -158
- basic_memory/sync/utils.py +0 -31
- basic_memory-0.7.0.dist-info/METADATA +0 -378
- basic_memory-0.7.0.dist-info/RECORD +0 -82
- {basic_memory-0.7.0.dist-info → basic_memory-0.16.1.dist-info}/WHEEL +0 -0
- {basic_memory-0.7.0.dist-info → basic_memory-0.16.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -3,142 +3,23 @@
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import json
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import Annotated
|
|
7
7
|
|
|
8
|
-
import logfire
|
|
9
8
|
import typer
|
|
9
|
+
from basic_memory.cli.app import claude_app
|
|
10
|
+
from basic_memory.config import get_project_config
|
|
11
|
+
from basic_memory.importers.claude_projects_importer import ClaudeProjectsImporter
|
|
12
|
+
from basic_memory.markdown import EntityParser, MarkdownProcessor
|
|
10
13
|
from loguru import logger
|
|
11
14
|
from rich.console import Console
|
|
12
15
|
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
16
|
|
|
20
17
|
console = Console()
|
|
21
18
|
|
|
22
19
|
|
|
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
20
|
async def get_markdown_processor() -> MarkdownProcessor:
|
|
141
21
|
"""Get MarkdownProcessor instance."""
|
|
22
|
+
config = get_project_config()
|
|
142
23
|
entity_parser = EntityParser(config.home)
|
|
143
24
|
return MarkdownProcessor(entity_parser)
|
|
144
25
|
|
|
@@ -161,36 +42,44 @@ def import_projects(
|
|
|
161
42
|
|
|
162
43
|
After importing, run 'basic-memory sync' to index the new files.
|
|
163
44
|
"""
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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)
|
|
45
|
+
config = get_project_config()
|
|
46
|
+
try:
|
|
47
|
+
if not projects_json.exists():
|
|
48
|
+
typer.echo(f"Error: File not found: {projects_json}", err=True)
|
|
196
49
|
raise typer.Exit(1)
|
|
50
|
+
|
|
51
|
+
# Get markdown processor
|
|
52
|
+
markdown_processor = asyncio.run(get_markdown_processor())
|
|
53
|
+
|
|
54
|
+
# Create the importer
|
|
55
|
+
importer = ClaudeProjectsImporter(config.home, markdown_processor)
|
|
56
|
+
|
|
57
|
+
# Process the file
|
|
58
|
+
base_path = config.home / base_folder if base_folder else config.home
|
|
59
|
+
console.print(f"\nImporting projects from {projects_json}...writing to {base_path}")
|
|
60
|
+
|
|
61
|
+
# Run the import
|
|
62
|
+
with projects_json.open("r", encoding="utf-8") as file:
|
|
63
|
+
json_data = json.load(file)
|
|
64
|
+
result = asyncio.run(importer.import_data(json_data, base_folder))
|
|
65
|
+
|
|
66
|
+
if not result.success: # pragma: no cover
|
|
67
|
+
typer.echo(f"Error during import: {result.error_message}", err=True)
|
|
68
|
+
raise typer.Exit(1)
|
|
69
|
+
|
|
70
|
+
# Show results
|
|
71
|
+
console.print(
|
|
72
|
+
Panel(
|
|
73
|
+
f"[green]Import complete![/green]\n\n"
|
|
74
|
+
f"Imported {result.documents} project documents\n"
|
|
75
|
+
f"Imported {result.prompts} prompt templates",
|
|
76
|
+
expand=False,
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
console.print("\nRun 'basic-memory sync' to index the new files.")
|
|
81
|
+
|
|
82
|
+
except Exception as e:
|
|
83
|
+
logger.error("Import failed")
|
|
84
|
+
typer.echo(f"Error during import: {e}", err=True)
|
|
85
|
+
raise typer.Exit(1)
|
|
@@ -3,97 +3,23 @@
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import json
|
|
5
5
|
from pathlib import Path
|
|
6
|
-
from typing import
|
|
6
|
+
from typing import Annotated
|
|
7
7
|
|
|
8
|
-
import logfire
|
|
9
8
|
import typer
|
|
9
|
+
from basic_memory.cli.app import import_app
|
|
10
|
+
from basic_memory.config import get_project_config
|
|
11
|
+
from basic_memory.importers.memory_json_importer import MemoryJsonImporter
|
|
12
|
+
from basic_memory.markdown import EntityParser, MarkdownProcessor
|
|
10
13
|
from loguru import logger
|
|
11
14
|
from rich.console import Console
|
|
12
15
|
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
16
|
|
|
20
17
|
console = Console()
|
|
21
18
|
|
|
22
19
|
|
|
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
20
|
async def get_markdown_processor() -> MarkdownProcessor:
|
|
96
21
|
"""Get MarkdownProcessor instance."""
|
|
22
|
+
config = get_project_config()
|
|
97
23
|
entity_parser = EntityParser(config.home)
|
|
98
24
|
return MarkdownProcessor(entity_parser)
|
|
99
25
|
|
|
@@ -103,6 +29,9 @@ def memory_json(
|
|
|
103
29
|
json_path: Annotated[Path, typer.Argument(..., help="Path to memory.json file")] = Path(
|
|
104
30
|
"memory.json"
|
|
105
31
|
),
|
|
32
|
+
destination_folder: Annotated[
|
|
33
|
+
str, typer.Option(help="Optional destination folder within the project")
|
|
34
|
+
] = "",
|
|
106
35
|
):
|
|
107
36
|
"""Import entities and relations from a memory.json file.
|
|
108
37
|
|
|
@@ -110,37 +39,48 @@ def memory_json(
|
|
|
110
39
|
1. Read entities and relations from the JSON file
|
|
111
40
|
2. Create markdown files for each entity
|
|
112
41
|
3. Include outgoing relations in each entity's markdown
|
|
113
|
-
|
|
114
|
-
After importing, run 'basic-memory sync' to index the new files.
|
|
115
42
|
"""
|
|
116
43
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
raise typer.Exit(1)
|
|
44
|
+
if not json_path.exists():
|
|
45
|
+
typer.echo(f"Error: File not found: {json_path}", err=True)
|
|
46
|
+
raise typer.Exit(1)
|
|
121
47
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
)
|
|
48
|
+
config = get_project_config()
|
|
49
|
+
try:
|
|
50
|
+
# Get markdown processor
|
|
51
|
+
markdown_processor = asyncio.run(get_markdown_processor())
|
|
52
|
+
|
|
53
|
+
# Create the importer
|
|
54
|
+
importer = MemoryJsonImporter(config.home, markdown_processor)
|
|
140
55
|
|
|
141
|
-
|
|
56
|
+
# Process the file
|
|
57
|
+
base_path = config.home if not destination_folder else config.home / destination_folder
|
|
58
|
+
console.print(f"\nImporting from {json_path}...writing to {base_path}")
|
|
142
59
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
60
|
+
# Run the import for json log format
|
|
61
|
+
file_data = []
|
|
62
|
+
with json_path.open("r", encoding="utf-8") as file:
|
|
63
|
+
for line in file:
|
|
64
|
+
json_data = json.loads(line)
|
|
65
|
+
file_data.append(json_data)
|
|
66
|
+
result = asyncio.run(importer.import_data(file_data, destination_folder))
|
|
67
|
+
|
|
68
|
+
if not result.success: # pragma: no cover
|
|
69
|
+
typer.echo(f"Error during import: {result.error_message}", err=True)
|
|
146
70
|
raise typer.Exit(1)
|
|
71
|
+
|
|
72
|
+
# Show results
|
|
73
|
+
console.print(
|
|
74
|
+
Panel(
|
|
75
|
+
f"[green]Import complete![/green]\n\n"
|
|
76
|
+
f"Created {result.entities} entities\n"
|
|
77
|
+
f"Added {result.relations} relations\n"
|
|
78
|
+
f"Skipped {result.skipped_entities} entities\n",
|
|
79
|
+
expand=False,
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logger.error("Import failed")
|
|
85
|
+
typer.echo(f"Error during import: {e}", err=True)
|
|
86
|
+
raise typer.Exit(1)
|
basic_memory/cli/commands/mcp.py
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
"""MCP server command."""
|
|
1
|
+
"""MCP server command with streamable HTTP transport."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
import typer
|
|
6
|
+
from typing import Optional
|
|
2
7
|
|
|
3
|
-
from loguru import logger
|
|
4
8
|
from basic_memory.cli.app import app
|
|
5
|
-
from basic_memory.config import
|
|
9
|
+
from basic_memory.config import ConfigManager
|
|
6
10
|
|
|
7
11
|
# Import mcp instance
|
|
8
12
|
from basic_memory.mcp.server import mcp as mcp_server # pragma: no cover
|
|
@@ -10,11 +14,81 @@ from basic_memory.mcp.server import mcp as mcp_server # pragma: no cover
|
|
|
10
14
|
# Import mcp tools to register them
|
|
11
15
|
import basic_memory.mcp.tools # noqa: F401 # pragma: no cover
|
|
12
16
|
|
|
17
|
+
# Import prompts to register them
|
|
18
|
+
import basic_memory.mcp.prompts # noqa: F401 # pragma: no cover
|
|
19
|
+
from loguru import logger
|
|
20
|
+
import threading
|
|
21
|
+
from basic_memory.services.initialization import initialize_file_sync
|
|
22
|
+
|
|
23
|
+
config = ConfigManager().config
|
|
24
|
+
|
|
25
|
+
if not config.cloud_mode_enabled:
|
|
26
|
+
|
|
27
|
+
@app.command()
|
|
28
|
+
def mcp(
|
|
29
|
+
transport: str = typer.Option(
|
|
30
|
+
"stdio", help="Transport type: stdio, streamable-http, or sse"
|
|
31
|
+
),
|
|
32
|
+
host: str = typer.Option(
|
|
33
|
+
"0.0.0.0", help="Host for HTTP transports (use 0.0.0.0 to allow external connections)"
|
|
34
|
+
),
|
|
35
|
+
port: int = typer.Option(8000, help="Port for HTTP transports"),
|
|
36
|
+
path: str = typer.Option("/mcp", help="Path prefix for streamable-http transport"),
|
|
37
|
+
project: Optional[str] = typer.Option(None, help="Restrict MCP server to single project"),
|
|
38
|
+
): # pragma: no cover
|
|
39
|
+
"""Run the MCP server with configurable transport options.
|
|
40
|
+
|
|
41
|
+
This command starts an MCP server using one of three transport options:
|
|
42
|
+
|
|
43
|
+
- stdio: Standard I/O (good for local usage)
|
|
44
|
+
- streamable-http: Recommended for web deployments (default)
|
|
45
|
+
- sse: Server-Sent Events (for compatibility with existing clients)
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
# Validate and set project constraint if specified
|
|
49
|
+
if project:
|
|
50
|
+
config_manager = ConfigManager()
|
|
51
|
+
project_name, _ = config_manager.get_project(project)
|
|
52
|
+
if not project_name:
|
|
53
|
+
typer.echo(f"No project found named: {project}", err=True)
|
|
54
|
+
raise typer.Exit(1)
|
|
55
|
+
|
|
56
|
+
# Set env var with validated project name
|
|
57
|
+
os.environ["BASIC_MEMORY_MCP_PROJECT"] = project_name
|
|
58
|
+
logger.info(f"MCP server constrained to project: {project_name}")
|
|
59
|
+
|
|
60
|
+
app_config = ConfigManager().config
|
|
61
|
+
|
|
62
|
+
def run_file_sync():
|
|
63
|
+
"""Run file sync in a separate thread with its own event loop."""
|
|
64
|
+
loop = asyncio.new_event_loop()
|
|
65
|
+
asyncio.set_event_loop(loop)
|
|
66
|
+
try:
|
|
67
|
+
loop.run_until_complete(initialize_file_sync(app_config))
|
|
68
|
+
except Exception as e:
|
|
69
|
+
logger.error(f"File sync error: {e}", err=True)
|
|
70
|
+
finally:
|
|
71
|
+
loop.close()
|
|
72
|
+
|
|
73
|
+
logger.info(f"Sync changes enabled: {app_config.sync_changes}")
|
|
74
|
+
if app_config.sync_changes:
|
|
75
|
+
# Start the sync thread
|
|
76
|
+
sync_thread = threading.Thread(target=run_file_sync, daemon=True)
|
|
77
|
+
sync_thread.start()
|
|
78
|
+
logger.info("Started file sync in background")
|
|
79
|
+
|
|
80
|
+
# Now run the MCP server (blocks)
|
|
81
|
+
logger.info(f"Starting MCP server with {transport.upper()} transport")
|
|
13
82
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
83
|
+
if transport == "stdio":
|
|
84
|
+
mcp_server.run(
|
|
85
|
+
transport=transport,
|
|
86
|
+
)
|
|
87
|
+
elif transport == "streamable-http" or transport == "sse":
|
|
88
|
+
mcp_server.run(
|
|
89
|
+
transport=transport,
|
|
90
|
+
host=host,
|
|
91
|
+
port=port,
|
|
92
|
+
path=path,
|
|
93
|
+
log_level="INFO",
|
|
94
|
+
)
|