basic-memory 0.2.12__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 (149) 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 +63 -31
  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 +165 -28
  18. basic_memory/api/routers/management_router.py +80 -0
  19. basic_memory/api/routers/memory_router.py +28 -67
  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 +219 -14
  23. basic_memory/api/routers/search_router.py +21 -13
  24. basic_memory/api/routers/utils.py +130 -0
  25. basic_memory/api/template_loader.py +292 -0
  26. basic_memory/cli/app.py +52 -1
  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 +26 -7
  41. basic_memory/cli/commands/import_chatgpt.py +83 -0
  42. basic_memory/cli/commands/import_claude_conversations.py +86 -0
  43. basic_memory/cli/commands/import_claude_projects.py +85 -0
  44. basic_memory/cli/commands/import_memory_json.py +35 -92
  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 +47 -30
  48. basic_memory/cli/commands/tool.py +341 -0
  49. basic_memory/cli/main.py +13 -6
  50. basic_memory/config.py +481 -22
  51. basic_memory/db.py +192 -32
  52. basic_memory/deps.py +252 -22
  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 -14
  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 +437 -59
  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 +188 -23
  112. basic_memory/schemas/project_info.py +211 -0
  113. basic_memory/schemas/prompt.py +90 -0
  114. basic_memory/schemas/request.py +57 -3
  115. basic_memory/schemas/response.py +9 -1
  116. basic_memory/schemas/search.py +33 -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 +251 -106
  120. basic_memory/services/directory_service.py +295 -0
  121. basic_memory/services/entity_service.py +595 -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 +50 -56
  126. basic_memory/services/project_service.py +863 -0
  127. basic_memory/services/search_service.py +172 -34
  128. basic_memory/sync/__init__.py +3 -2
  129. basic_memory/sync/background_sync.py +26 -0
  130. basic_memory/sync/sync_service.py +1176 -96
  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 +388 -28
  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.2.12.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 -203
  140. basic_memory/mcp/tools/knowledge.py +0 -56
  141. basic_memory/mcp/tools/memory.py +0 -151
  142. basic_memory/mcp/tools/notes.py +0 -122
  143. basic_memory/schemas/discovery.py +0 -28
  144. basic_memory/sync/file_change_scanner.py +0 -158
  145. basic_memory/sync/utils.py +0 -34
  146. basic_memory-0.2.12.dist-info/METADATA +0 -291
  147. basic_memory-0.2.12.dist-info/RECORD +0 -78
  148. {basic_memory-0.2.12.dist-info → basic_memory-0.16.1.dist-info}/WHEEL +0 -0
  149. {basic_memory-0.2.12.dist-info → basic_memory-0.16.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,85 @@
1
+ """Import command for basic-memory CLI to import project data from Claude.ai."""
2
+
3
+ import asyncio
4
+ import json
5
+ from pathlib import Path
6
+ from typing import Annotated
7
+
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
13
+ from loguru import logger
14
+ from rich.console import Console
15
+ from rich.panel import Panel
16
+
17
+ console = Console()
18
+
19
+
20
+ async def get_markdown_processor() -> MarkdownProcessor:
21
+ """Get MarkdownProcessor instance."""
22
+ config = get_project_config()
23
+ entity_parser = EntityParser(config.home)
24
+ return MarkdownProcessor(entity_parser)
25
+
26
+
27
+ @claude_app.command(name="projects", help="Import projects from Claude.ai.")
28
+ def import_projects(
29
+ projects_json: Annotated[Path, typer.Argument(..., help="Path to projects.json file")] = Path(
30
+ "projects.json"
31
+ ),
32
+ base_folder: Annotated[
33
+ str, typer.Option(help="The base folder to place project files in.")
34
+ ] = "projects",
35
+ ):
36
+ """Import project data from Claude.ai.
37
+
38
+ This command will:
39
+ 1. Create a directory for each project
40
+ 2. Store docs in a docs/ subdirectory
41
+ 3. Place prompt template in project root
42
+
43
+ After importing, run 'basic-memory sync' to index the new files.
44
+ """
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)
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,104 +3,35 @@
3
3
  import asyncio
4
4
  import json
5
5
  from pathlib import Path
6
- from typing import Dict, Any, List
6
+ from typing import Annotated
7
7
 
8
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
9
13
  from loguru import logger
10
14
  from rich.console import Console
11
15
  from rich.panel import Panel
12
- from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
13
-
14
- from basic_memory.cli.app import app
15
- from basic_memory.config import config
16
- from basic_memory.markdown import EntityParser, MarkdownProcessor
17
- from basic_memory.markdown.schemas import EntityMarkdown, EntityFrontmatter, Observation, Relation
18
-
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
 
100
26
 
101
- @app.command()
102
- def import_json(
103
- json_path: Path = typer.Argument(..., help="Path to memory.json file to import"),
27
+ @import_app.command()
28
+ def memory_json(
29
+ json_path: Annotated[Path, typer.Argument(..., help="Path to memory.json file")] = Path(
30
+ "memory.json"
31
+ ),
32
+ destination_folder: Annotated[
33
+ str, typer.Option(help="Optional destination folder within the project")
34
+ ] = "",
104
35
  ):
105
36
  """Import entities and relations from a memory.json file.
106
37
 
@@ -108,36 +39,48 @@ def import_json(
108
39
  1. Read entities and relations from the JSON file
109
40
  2. Create markdown files for each entity
110
41
  3. Include outgoing relations in each entity's markdown
111
-
112
- After importing, run 'basic-memory sync' to index the new files.
113
42
  """
114
43
 
115
44
  if not json_path.exists():
116
45
  typer.echo(f"Error: File not found: {json_path}", err=True)
117
46
  raise typer.Exit(1)
118
47
 
48
+ config = get_project_config()
119
49
  try:
120
50
  # Get markdown processor
121
51
  markdown_processor = asyncio.run(get_markdown_processor())
122
52
 
53
+ # Create the importer
54
+ importer = MemoryJsonImporter(config.home, markdown_processor)
55
+
123
56
  # Process the file
124
- base_path = config.home
57
+ base_path = config.home if not destination_folder else config.home / destination_folder
125
58
  console.print(f"\nImporting from {json_path}...writing to {base_path}")
126
- results = asyncio.run(process_memory_json(json_path, base_path, markdown_processor))
59
+
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)
70
+ raise typer.Exit(1)
127
71
 
128
72
  # Show results
129
73
  console.print(
130
74
  Panel(
131
75
  f"[green]Import complete![/green]\n\n"
132
- f"Created {results['entities']} entities\n"
133
- f"Added {results['relations']} relations",
76
+ f"Created {result.entities} entities\n"
77
+ f"Added {result.relations} relations\n"
78
+ f"Skipped {result.skipped_entities} entities\n",
134
79
  expand=False,
135
80
  )
136
81
  )
137
82
 
138
- console.print("\nRun 'basic-memory sync' to index the new files.")
139
-
140
83
  except Exception as e:
141
- logger.exception("Import failed")
84
+ logger.error("Import failed")
142
85
  typer.echo(f"Error during import: {e}", err=True)
143
86
  raise typer.Exit(1)
@@ -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 config
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
- @app.command()
15
- def mcp():
16
- """Run the MCP server for Claude Desktop integration."""
17
- home_dir = config.home
18
- logger.info("Starting Basic Memory MCP server")
19
- logger.info(f"Home directory: {home_dir}")
20
- mcp_server.run()
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
+ )