basic-memory 0.15.0__py3-none-any.whl → 0.15.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 +1 -1
- basic_memory/api/routers/directory_router.py +23 -2
- basic_memory/api/routers/project_router.py +1 -0
- basic_memory/cli/auth.py +2 -2
- basic_memory/cli/commands/command_utils.py +11 -28
- basic_memory/cli/commands/mcp.py +72 -67
- basic_memory/cli/commands/project.py +54 -49
- basic_memory/cli/commands/status.py +6 -15
- basic_memory/config.py +55 -9
- basic_memory/deps.py +7 -5
- basic_memory/ignore_utils.py +7 -7
- basic_memory/mcp/async_client.py +102 -4
- basic_memory/mcp/prompts/continue_conversation.py +16 -15
- basic_memory/mcp/prompts/search.py +12 -11
- basic_memory/mcp/resources/ai_assistant_guide.md +185 -453
- basic_memory/mcp/resources/project_info.py +9 -7
- basic_memory/mcp/tools/build_context.py +40 -39
- basic_memory/mcp/tools/canvas.py +21 -20
- basic_memory/mcp/tools/chatgpt_tools.py +11 -2
- basic_memory/mcp/tools/delete_note.py +22 -21
- basic_memory/mcp/tools/edit_note.py +105 -104
- basic_memory/mcp/tools/list_directory.py +98 -95
- basic_memory/mcp/tools/move_note.py +127 -125
- basic_memory/mcp/tools/project_management.py +101 -98
- basic_memory/mcp/tools/read_content.py +64 -63
- basic_memory/mcp/tools/read_note.py +88 -88
- basic_memory/mcp/tools/recent_activity.py +139 -135
- basic_memory/mcp/tools/search.py +27 -26
- basic_memory/mcp/tools/sync_status.py +133 -128
- basic_memory/mcp/tools/utils.py +0 -15
- basic_memory/mcp/tools/view_note.py +14 -28
- basic_memory/mcp/tools/write_note.py +97 -87
- basic_memory/repository/entity_repository.py +60 -0
- basic_memory/repository/repository.py +16 -3
- basic_memory/repository/search_repository.py +42 -0
- basic_memory/schemas/project_info.py +1 -1
- basic_memory/services/directory_service.py +124 -3
- basic_memory/services/entity_service.py +31 -9
- basic_memory/services/project_service.py +97 -10
- basic_memory/services/search_service.py +16 -8
- basic_memory/sync/sync_service.py +28 -13
- {basic_memory-0.15.0.dist-info → basic_memory-0.15.1.dist-info}/METADATA +51 -4
- {basic_memory-0.15.0.dist-info → basic_memory-0.15.1.dist-info}/RECORD +46 -47
- basic_memory/mcp/tools/headers.py +0 -44
- {basic_memory-0.15.0.dist-info → basic_memory-0.15.1.dist-info}/WHEEL +0 -0
- {basic_memory-0.15.0.dist-info → basic_memory-0.15.1.dist-info}/entry_points.txt +0 -0
- {basic_memory-0.15.0.dist-info → basic_memory-0.15.1.dist-info}/licenses/LICENSE +0 -0
basic_memory/ignore_utils.py
CHANGED
|
@@ -11,9 +11,9 @@ DEFAULT_IGNORE_PATTERNS = {
|
|
|
11
11
|
# Hidden files (files starting with dot)
|
|
12
12
|
".*",
|
|
13
13
|
# Basic Memory internal files
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
14
|
+
"*.db",
|
|
15
|
+
"*.db-shm",
|
|
16
|
+
"*.db-wal",
|
|
17
17
|
"config.json",
|
|
18
18
|
# Version control
|
|
19
19
|
".git",
|
|
@@ -84,10 +84,10 @@ def create_default_bmignore() -> None:
|
|
|
84
84
|
# Hidden files (files starting with dot)
|
|
85
85
|
.*
|
|
86
86
|
|
|
87
|
-
# Basic Memory internal files
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
# Basic Memory internal files (includes test databases)
|
|
88
|
+
*.db
|
|
89
|
+
*.db-shm
|
|
90
|
+
*.db-wal
|
|
91
91
|
config.json
|
|
92
92
|
|
|
93
93
|
# Version control
|
basic_memory/mcp/async_client.py
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager, AbstractAsyncContextManager
|
|
2
|
+
from typing import AsyncIterator, Callable, Optional
|
|
3
|
+
|
|
1
4
|
from httpx import ASGITransport, AsyncClient, Timeout
|
|
2
5
|
from loguru import logger
|
|
3
6
|
|
|
@@ -5,9 +8,108 @@ from basic_memory.api.app import app as fastapi_app
|
|
|
5
8
|
from basic_memory.config import ConfigManager
|
|
6
9
|
|
|
7
10
|
|
|
11
|
+
# Optional factory override for dependency injection
|
|
12
|
+
_client_factory: Optional[Callable[[], AbstractAsyncContextManager[AsyncClient]]] = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def set_client_factory(factory: Callable[[], AbstractAsyncContextManager[AsyncClient]]) -> None:
|
|
16
|
+
"""Override the default client factory (for cloud app, testing, etc).
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
factory: An async context manager that yields an AsyncClient
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
@asynccontextmanager
|
|
23
|
+
async def custom_client_factory():
|
|
24
|
+
async with AsyncClient(...) as client:
|
|
25
|
+
yield client
|
|
26
|
+
|
|
27
|
+
set_client_factory(custom_client_factory)
|
|
28
|
+
"""
|
|
29
|
+
global _client_factory
|
|
30
|
+
_client_factory = factory
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@asynccontextmanager
|
|
34
|
+
async def get_client() -> AsyncIterator[AsyncClient]:
|
|
35
|
+
"""Get an AsyncClient as a context manager.
|
|
36
|
+
|
|
37
|
+
This function provides proper resource management for HTTP clients,
|
|
38
|
+
ensuring connections are closed after use. It supports three modes:
|
|
39
|
+
|
|
40
|
+
1. **Factory injection** (cloud app, tests):
|
|
41
|
+
If a custom factory is set via set_client_factory(), use that.
|
|
42
|
+
|
|
43
|
+
2. **CLI cloud mode**:
|
|
44
|
+
When cloud_mode_enabled is True, create HTTP client with auth
|
|
45
|
+
token from CLIAuth for requests to cloud proxy endpoint.
|
|
46
|
+
|
|
47
|
+
3. **Local mode** (default):
|
|
48
|
+
Use ASGI transport for in-process requests to local FastAPI app.
|
|
49
|
+
|
|
50
|
+
Usage:
|
|
51
|
+
async with get_client() as client:
|
|
52
|
+
response = await client.get("/path")
|
|
53
|
+
|
|
54
|
+
Yields:
|
|
55
|
+
AsyncClient: Configured HTTP client for the current mode
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
RuntimeError: If cloud mode is enabled but user is not authenticated
|
|
59
|
+
"""
|
|
60
|
+
if _client_factory:
|
|
61
|
+
# Use injected factory (cloud app, tests)
|
|
62
|
+
async with _client_factory() as client:
|
|
63
|
+
yield client
|
|
64
|
+
else:
|
|
65
|
+
# Default: create based on config
|
|
66
|
+
config = ConfigManager().config
|
|
67
|
+
timeout = Timeout(
|
|
68
|
+
connect=10.0, # 10 seconds for connection
|
|
69
|
+
read=30.0, # 30 seconds for reading response
|
|
70
|
+
write=30.0, # 30 seconds for writing request
|
|
71
|
+
pool=30.0, # 30 seconds for connection pool
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if config.cloud_mode_enabled:
|
|
75
|
+
# CLI cloud mode: inject auth when creating client
|
|
76
|
+
from basic_memory.cli.auth import CLIAuth
|
|
77
|
+
|
|
78
|
+
auth = CLIAuth(client_id=config.cloud_client_id, authkit_domain=config.cloud_domain)
|
|
79
|
+
token = await auth.get_valid_token()
|
|
80
|
+
|
|
81
|
+
if not token:
|
|
82
|
+
raise RuntimeError(
|
|
83
|
+
"Cloud mode enabled but not authenticated. "
|
|
84
|
+
"Run 'basic-memory cloud login' first."
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Auth header set ONCE at client creation
|
|
88
|
+
proxy_base_url = f"{config.cloud_host}/proxy"
|
|
89
|
+
logger.info(f"Creating HTTP client for cloud proxy at: {proxy_base_url}")
|
|
90
|
+
async with AsyncClient(
|
|
91
|
+
base_url=proxy_base_url,
|
|
92
|
+
headers={"Authorization": f"Bearer {token}"},
|
|
93
|
+
timeout=timeout,
|
|
94
|
+
) as client:
|
|
95
|
+
yield client
|
|
96
|
+
else:
|
|
97
|
+
# Local mode: ASGI transport for in-process calls
|
|
98
|
+
logger.info("Creating ASGI client for local Basic Memory API")
|
|
99
|
+
async with AsyncClient(
|
|
100
|
+
transport=ASGITransport(app=fastapi_app), base_url="http://test", timeout=timeout
|
|
101
|
+
) as client:
|
|
102
|
+
yield client
|
|
103
|
+
|
|
104
|
+
|
|
8
105
|
def create_client() -> AsyncClient:
|
|
9
106
|
"""Create an HTTP client based on configuration.
|
|
10
107
|
|
|
108
|
+
DEPRECATED: Use get_client() context manager instead for proper resource management.
|
|
109
|
+
|
|
110
|
+
This function is kept for backward compatibility but will be removed in a future version.
|
|
111
|
+
The returned client should be closed manually by calling await client.aclose().
|
|
112
|
+
|
|
11
113
|
Returns:
|
|
12
114
|
AsyncClient configured for either local ASGI or remote proxy
|
|
13
115
|
"""
|
|
@@ -34,7 +136,3 @@ def create_client() -> AsyncClient:
|
|
|
34
136
|
return AsyncClient(
|
|
35
137
|
transport=ASGITransport(app=fastapi_app), base_url="http://test", timeout=timeout
|
|
36
138
|
)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
# Create shared async client
|
|
40
|
-
client = create_client()
|
|
@@ -10,7 +10,7 @@ from loguru import logger
|
|
|
10
10
|
from pydantic import Field
|
|
11
11
|
|
|
12
12
|
from basic_memory.config import get_project_config
|
|
13
|
-
from basic_memory.mcp.async_client import
|
|
13
|
+
from basic_memory.mcp.async_client import get_client
|
|
14
14
|
from basic_memory.mcp.server import mcp
|
|
15
15
|
from basic_memory.mcp.tools.utils import call_post
|
|
16
16
|
from basic_memory.schemas.base import TimeFrame
|
|
@@ -42,20 +42,21 @@ async def continue_conversation(
|
|
|
42
42
|
"""
|
|
43
43
|
logger.info(f"Continuing session, topic: {topic}, timeframe: {timeframe}")
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
async with get_client() as client:
|
|
46
|
+
# Create request model
|
|
47
|
+
request = ContinueConversationRequest( # pyright: ignore [reportCallIssue]
|
|
48
|
+
topic=topic, timeframe=timeframe
|
|
49
|
+
)
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
project_url = get_project_config().project_url
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
# Call the prompt API endpoint
|
|
54
|
+
response = await call_post(
|
|
55
|
+
client,
|
|
56
|
+
f"{project_url}/prompt/continue-conversation",
|
|
57
|
+
json=request.model_dump(exclude_none=True),
|
|
58
|
+
)
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
# Extract the rendered prompt from the response
|
|
61
|
+
result = response.json()
|
|
62
|
+
return result["prompt"]
|
|
@@ -9,7 +9,7 @@ from loguru import logger
|
|
|
9
9
|
from pydantic import Field
|
|
10
10
|
|
|
11
11
|
from basic_memory.config import get_project_config
|
|
12
|
-
from basic_memory.mcp.async_client import
|
|
12
|
+
from basic_memory.mcp.async_client import get_client
|
|
13
13
|
from basic_memory.mcp.server import mcp
|
|
14
14
|
from basic_memory.mcp.tools.utils import call_post
|
|
15
15
|
from basic_memory.schemas.base import TimeFrame
|
|
@@ -41,16 +41,17 @@ async def search_prompt(
|
|
|
41
41
|
"""
|
|
42
42
|
logger.info(f"Searching knowledge base, query: {query}, timeframe: {timeframe}")
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
async with get_client() as client:
|
|
45
|
+
# Create request model
|
|
46
|
+
request = SearchPromptRequest(query=query, timeframe=timeframe)
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
project_url = get_project_config().project_url
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
# Call the prompt API endpoint
|
|
51
|
+
response = await call_post(
|
|
52
|
+
client, f"{project_url}/prompt/search", json=request.model_dump(exclude_none=True)
|
|
53
|
+
)
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
# Extract the rendered prompt from the response
|
|
56
|
+
result = response.json()
|
|
57
|
+
return result["prompt"]
|