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.
- basic_memory/__init__.py +5 -1
- basic_memory/alembic/alembic.ini +119 -0
- basic_memory/alembic/env.py +130 -20
- basic_memory/alembic/migrations.py +4 -9
- basic_memory/alembic/versions/314f1ea54dc4_add_postgres_full_text_search_support_.py +131 -0
- basic_memory/alembic/versions/502b60eaa905_remove_required_from_entity_permalink.py +51 -0
- basic_memory/alembic/versions/5fe1ab1ccebe_add_projects_table.py +120 -0
- basic_memory/alembic/versions/647e7a75e2cd_project_constraint_fix.py +112 -0
- basic_memory/alembic/versions/6830751f5fb6_merge_multiple_heads.py +24 -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/a2b3c4d5e6f7_add_search_index_entity_cascade.py +56 -0
- basic_memory/alembic/versions/b3c3938bacdb_relation_to_name_unique_index.py +44 -0
- basic_memory/alembic/versions/cc7172b46608_update_search_index_schema.py +113 -0
- basic_memory/alembic/versions/e7e1f4367280_add_scan_watermark_tracking_to_project.py +37 -0
- basic_memory/alembic/versions/f8a9b2c3d4e5_add_pg_trgm_for_fuzzy_link_resolution.py +239 -0
- basic_memory/alembic/versions/g9a0b3c4d5e6_add_external_id_to_project_and_entity.py +173 -0
- basic_memory/api/app.py +87 -20
- basic_memory/api/container.py +133 -0
- 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 +180 -23
- 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 +460 -0
- basic_memory/api/routers/prompt_router.py +260 -0
- basic_memory/api/routers/resource_router.py +136 -11
- basic_memory/api/routers/search_router.py +5 -5
- basic_memory/api/routers/utils.py +169 -0
- basic_memory/api/template_loader.py +292 -0
- basic_memory/api/v2/__init__.py +35 -0
- basic_memory/api/v2/routers/__init__.py +21 -0
- basic_memory/api/v2/routers/directory_router.py +93 -0
- basic_memory/api/v2/routers/importer_router.py +181 -0
- basic_memory/api/v2/routers/knowledge_router.py +427 -0
- basic_memory/api/v2/routers/memory_router.py +130 -0
- basic_memory/api/v2/routers/project_router.py +359 -0
- basic_memory/api/v2/routers/prompt_router.py +269 -0
- basic_memory/api/v2/routers/resource_router.py +286 -0
- basic_memory/api/v2/routers/search_router.py +73 -0
- basic_memory/cli/app.py +80 -10
- basic_memory/cli/auth.py +300 -0
- basic_memory/cli/commands/__init__.py +15 -2
- basic_memory/cli/commands/cloud/__init__.py +6 -0
- basic_memory/cli/commands/cloud/api_client.py +127 -0
- basic_memory/cli/commands/cloud/bisync_commands.py +110 -0
- basic_memory/cli/commands/cloud/cloud_utils.py +108 -0
- basic_memory/cli/commands/cloud/core_commands.py +195 -0
- basic_memory/cli/commands/cloud/rclone_commands.py +397 -0
- basic_memory/cli/commands/cloud/rclone_config.py +110 -0
- basic_memory/cli/commands/cloud/rclone_installer.py +263 -0
- basic_memory/cli/commands/cloud/upload.py +240 -0
- basic_memory/cli/commands/cloud/upload_command.py +124 -0
- basic_memory/cli/commands/command_utils.py +99 -0
- basic_memory/cli/commands/db.py +87 -12
- basic_memory/cli/commands/format.py +198 -0
- basic_memory/cli/commands/import_chatgpt.py +47 -223
- basic_memory/cli/commands/import_claude_conversations.py +48 -171
- basic_memory/cli/commands/import_claude_projects.py +53 -160
- basic_memory/cli/commands/import_memory_json.py +55 -111
- basic_memory/cli/commands/mcp.py +67 -11
- basic_memory/cli/commands/project.py +889 -0
- basic_memory/cli/commands/status.py +52 -34
- basic_memory/cli/commands/telemetry.py +81 -0
- basic_memory/cli/commands/tool.py +341 -0
- basic_memory/cli/container.py +84 -0
- basic_memory/cli/main.py +14 -6
- basic_memory/config.py +580 -26
- basic_memory/db.py +285 -28
- basic_memory/deps/__init__.py +293 -0
- basic_memory/deps/config.py +26 -0
- basic_memory/deps/db.py +56 -0
- basic_memory/deps/importers.py +200 -0
- basic_memory/deps/projects.py +238 -0
- basic_memory/deps/repositories.py +179 -0
- basic_memory/deps/services.py +480 -0
- basic_memory/deps.py +16 -185
- basic_memory/file_utils.py +318 -54
- basic_memory/ignore_utils.py +297 -0
- basic_memory/importers/__init__.py +27 -0
- basic_memory/importers/base.py +100 -0
- basic_memory/importers/chatgpt_importer.py +245 -0
- basic_memory/importers/claude_conversations_importer.py +192 -0
- basic_memory/importers/claude_projects_importer.py +184 -0
- basic_memory/importers/memory_json_importer.py +128 -0
- basic_memory/importers/utils.py +61 -0
- basic_memory/markdown/entity_parser.py +182 -23
- basic_memory/markdown/markdown_processor.py +70 -7
- basic_memory/markdown/plugins.py +43 -23
- basic_memory/markdown/schemas.py +1 -1
- basic_memory/markdown/utils.py +38 -14
- basic_memory/mcp/async_client.py +135 -4
- basic_memory/mcp/clients/__init__.py +28 -0
- basic_memory/mcp/clients/directory.py +70 -0
- basic_memory/mcp/clients/knowledge.py +176 -0
- basic_memory/mcp/clients/memory.py +120 -0
- basic_memory/mcp/clients/project.py +89 -0
- basic_memory/mcp/clients/resource.py +71 -0
- basic_memory/mcp/clients/search.py +65 -0
- basic_memory/mcp/container.py +110 -0
- basic_memory/mcp/project_context.py +155 -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 +61 -9
- basic_memory/mcp/tools/__init__.py +33 -21
- basic_memory/mcp/tools/build_context.py +120 -0
- basic_memory/mcp/tools/canvas.py +152 -0
- basic_memory/mcp/tools/chatgpt_tools.py +190 -0
- basic_memory/mcp/tools/delete_note.py +249 -0
- basic_memory/mcp/tools/edit_note.py +325 -0
- basic_memory/mcp/tools/list_directory.py +157 -0
- basic_memory/mcp/tools/move_note.py +549 -0
- basic_memory/mcp/tools/project_management.py +204 -0
- basic_memory/mcp/tools/read_content.py +281 -0
- basic_memory/mcp/tools/read_note.py +265 -0
- basic_memory/mcp/tools/recent_activity.py +528 -0
- basic_memory/mcp/tools/search.py +377 -24
- basic_memory/mcp/tools/utils.py +402 -16
- basic_memory/mcp/tools/view_note.py +78 -0
- basic_memory/mcp/tools/write_note.py +230 -0
- basic_memory/models/__init__.py +3 -2
- basic_memory/models/knowledge.py +82 -17
- basic_memory/models/project.py +93 -0
- basic_memory/models/search.py +68 -8
- basic_memory/project_resolver.py +222 -0
- basic_memory/repository/__init__.py +2 -0
- basic_memory/repository/entity_repository.py +437 -8
- basic_memory/repository/observation_repository.py +36 -3
- basic_memory/repository/postgres_search_repository.py +451 -0
- basic_memory/repository/project_info_repository.py +10 -0
- basic_memory/repository/project_repository.py +140 -0
- basic_memory/repository/relation_repository.py +79 -4
- basic_memory/repository/repository.py +148 -29
- basic_memory/repository/search_index_row.py +95 -0
- basic_memory/repository/search_repository.py +79 -268
- basic_memory/repository/search_repository_base.py +241 -0
- basic_memory/repository/sqlite_search_repository.py +437 -0
- basic_memory/runtime.py +61 -0
- basic_memory/schemas/__init__.py +22 -9
- basic_memory/schemas/base.py +131 -12
- basic_memory/schemas/cloud.py +50 -0
- basic_memory/schemas/directory.py +31 -0
- basic_memory/schemas/importer.py +35 -0
- basic_memory/schemas/memory.py +194 -25
- basic_memory/schemas/project_info.py +213 -0
- basic_memory/schemas/prompt.py +90 -0
- basic_memory/schemas/request.py +56 -2
- basic_memory/schemas/response.py +85 -28
- basic_memory/schemas/search.py +36 -35
- basic_memory/schemas/sync_report.py +72 -0
- basic_memory/schemas/v2/__init__.py +27 -0
- basic_memory/schemas/v2/entity.py +133 -0
- basic_memory/schemas/v2/resource.py +47 -0
- basic_memory/services/__init__.py +2 -1
- basic_memory/services/context_service.py +451 -138
- basic_memory/services/directory_service.py +310 -0
- basic_memory/services/entity_service.py +636 -71
- basic_memory/services/exceptions.py +21 -0
- basic_memory/services/file_service.py +402 -33
- basic_memory/services/initialization.py +216 -0
- basic_memory/services/link_resolver.py +50 -56
- basic_memory/services/project_service.py +888 -0
- basic_memory/services/search_service.py +232 -37
- basic_memory/sync/__init__.py +4 -2
- basic_memory/sync/background_sync.py +26 -0
- basic_memory/sync/coordinator.py +160 -0
- basic_memory/sync/sync_service.py +1200 -109
- basic_memory/sync/watch_service.py +432 -135
- basic_memory/telemetry.py +249 -0
- basic_memory/templates/prompts/continue_conversation.hbs +110 -0
- basic_memory/templates/prompts/search.hbs +101 -0
- basic_memory/utils.py +407 -54
- basic_memory-0.17.4.dist-info/METADATA +617 -0
- basic_memory-0.17.4.dist-info/RECORD +193 -0
- {basic_memory-0.7.0.dist-info → basic_memory-0.17.4.dist-info}/WHEEL +1 -1
- {basic_memory-0.7.0.dist-info → basic_memory-0.17.4.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.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
|
|
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
|
|
24
|
-
"""
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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
|
|
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
|
|
24
|
-
|
|
25
|
-
)
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
-
)
|
|
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
|
-
|
|
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
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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)
|
basic_memory/cli/commands/mcp.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
+
)
|