basic-memory 0.14.2__py3-none-any.whl → 0.14.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 +1 -1
- basic_memory/alembic/env.py +3 -1
- basic_memory/alembic/versions/a1b2c3d4e5f6_fix_project_foreign_keys.py +53 -0
- basic_memory/api/app.py +4 -1
- basic_memory/api/routers/management_router.py +3 -1
- basic_memory/api/routers/project_router.py +21 -13
- basic_memory/api/routers/resource_router.py +3 -3
- basic_memory/cli/app.py +3 -3
- basic_memory/cli/commands/__init__.py +1 -2
- basic_memory/cli/commands/db.py +5 -5
- basic_memory/cli/commands/import_chatgpt.py +3 -2
- basic_memory/cli/commands/import_claude_conversations.py +3 -1
- basic_memory/cli/commands/import_claude_projects.py +3 -1
- basic_memory/cli/commands/import_memory_json.py +5 -2
- basic_memory/cli/commands/mcp.py +3 -15
- basic_memory/cli/commands/project.py +46 -6
- basic_memory/cli/commands/status.py +4 -1
- basic_memory/cli/commands/sync.py +10 -2
- basic_memory/cli/main.py +0 -1
- basic_memory/config.py +61 -34
- basic_memory/db.py +2 -6
- basic_memory/deps.py +3 -2
- basic_memory/file_utils.py +65 -0
- basic_memory/importers/chatgpt_importer.py +20 -10
- basic_memory/importers/memory_json_importer.py +22 -7
- basic_memory/importers/utils.py +2 -2
- basic_memory/markdown/entity_parser.py +2 -2
- basic_memory/markdown/markdown_processor.py +2 -2
- basic_memory/markdown/plugins.py +42 -26
- basic_memory/markdown/utils.py +1 -1
- basic_memory/mcp/async_client.py +22 -2
- basic_memory/mcp/project_session.py +6 -4
- basic_memory/mcp/prompts/__init__.py +0 -2
- basic_memory/mcp/server.py +8 -71
- basic_memory/mcp/tools/build_context.py +12 -2
- basic_memory/mcp/tools/move_note.py +24 -12
- basic_memory/mcp/tools/project_management.py +22 -7
- basic_memory/mcp/tools/read_content.py +16 -0
- basic_memory/mcp/tools/read_note.py +17 -2
- basic_memory/mcp/tools/sync_status.py +3 -2
- basic_memory/mcp/tools/write_note.py +9 -1
- basic_memory/models/knowledge.py +13 -2
- basic_memory/models/project.py +3 -3
- basic_memory/repository/entity_repository.py +2 -2
- basic_memory/repository/project_repository.py +19 -1
- basic_memory/repository/search_repository.py +7 -3
- basic_memory/schemas/base.py +40 -10
- basic_memory/schemas/importer.py +1 -0
- basic_memory/schemas/memory.py +23 -11
- basic_memory/services/context_service.py +12 -2
- basic_memory/services/directory_service.py +7 -0
- basic_memory/services/entity_service.py +56 -10
- basic_memory/services/initialization.py +0 -75
- basic_memory/services/project_service.py +93 -36
- basic_memory/sync/background_sync.py +4 -3
- basic_memory/sync/sync_service.py +53 -4
- basic_memory/sync/watch_service.py +31 -8
- basic_memory/utils.py +234 -71
- {basic_memory-0.14.2.dist-info → basic_memory-0.14.4.dist-info}/METADATA +21 -92
- {basic_memory-0.14.2.dist-info → basic_memory-0.14.4.dist-info}/RECORD +63 -68
- basic_memory/cli/commands/auth.py +0 -136
- basic_memory/mcp/auth_provider.py +0 -270
- basic_memory/mcp/external_auth_provider.py +0 -321
- basic_memory/mcp/prompts/sync_status.py +0 -112
- basic_memory/mcp/supabase_auth_provider.py +0 -463
- basic_memory/services/migration_service.py +0 -168
- {basic_memory-0.14.2.dist-info → basic_memory-0.14.4.dist-info}/WHEEL +0 -0
- {basic_memory-0.14.2.dist-info → basic_memory-0.14.4.dist-info}/entry_points.txt +0 -0
- {basic_memory-0.14.2.dist-info → basic_memory-0.14.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
"""External OAuth provider integration for Basic Memory MCP server."""
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
from typing import Optional, Dict, Any
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
-
|
|
7
|
-
import httpx
|
|
8
|
-
from loguru import logger
|
|
9
|
-
from mcp.server.auth.provider import (
|
|
10
|
-
OAuthAuthorizationServerProvider,
|
|
11
|
-
AuthorizationParams,
|
|
12
|
-
AuthorizationCode,
|
|
13
|
-
RefreshToken,
|
|
14
|
-
AccessToken,
|
|
15
|
-
construct_redirect_uri,
|
|
16
|
-
)
|
|
17
|
-
from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@dataclass
|
|
21
|
-
class ExternalAuthorizationCode(AuthorizationCode):
|
|
22
|
-
"""Authorization code with external provider metadata."""
|
|
23
|
-
|
|
24
|
-
external_code: Optional[str] = None
|
|
25
|
-
state: Optional[str] = None
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
@dataclass
|
|
29
|
-
class ExternalRefreshToken(RefreshToken):
|
|
30
|
-
"""Refresh token with external provider metadata."""
|
|
31
|
-
|
|
32
|
-
external_token: Optional[str] = None
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
@dataclass
|
|
36
|
-
class ExternalAccessToken(AccessToken):
|
|
37
|
-
"""Access token with external provider metadata."""
|
|
38
|
-
|
|
39
|
-
external_token: Optional[str] = None
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class ExternalOAuthProvider(
|
|
43
|
-
OAuthAuthorizationServerProvider[
|
|
44
|
-
ExternalAuthorizationCode, ExternalRefreshToken, ExternalAccessToken
|
|
45
|
-
]
|
|
46
|
-
):
|
|
47
|
-
"""OAuth provider that delegates to external OAuth providers.
|
|
48
|
-
|
|
49
|
-
This provider can integrate with services like:
|
|
50
|
-
- GitHub OAuth
|
|
51
|
-
- Google OAuth
|
|
52
|
-
- Auth0
|
|
53
|
-
- Okta
|
|
54
|
-
"""
|
|
55
|
-
|
|
56
|
-
def __init__(
|
|
57
|
-
self,
|
|
58
|
-
issuer_url: str,
|
|
59
|
-
external_provider: str,
|
|
60
|
-
external_client_id: str,
|
|
61
|
-
external_client_secret: str,
|
|
62
|
-
external_authorize_url: str,
|
|
63
|
-
external_token_url: str,
|
|
64
|
-
external_userinfo_url: Optional[str] = None,
|
|
65
|
-
):
|
|
66
|
-
self.issuer_url = issuer_url
|
|
67
|
-
self.external_provider = external_provider
|
|
68
|
-
self.external_client_id = external_client_id
|
|
69
|
-
self.external_client_secret = external_client_secret
|
|
70
|
-
self.external_authorize_url = external_authorize_url
|
|
71
|
-
self.external_token_url = external_token_url
|
|
72
|
-
self.external_userinfo_url = external_userinfo_url
|
|
73
|
-
|
|
74
|
-
# In-memory storage - in production, use a database
|
|
75
|
-
self.clients: Dict[str, OAuthClientInformationFull] = {}
|
|
76
|
-
self.codes: Dict[str, ExternalAuthorizationCode] = {}
|
|
77
|
-
self.tokens: Dict[str, Any] = {}
|
|
78
|
-
|
|
79
|
-
self.http_client = httpx.AsyncClient()
|
|
80
|
-
|
|
81
|
-
async def get_client(self, client_id: str) -> Optional[OAuthClientInformationFull]:
|
|
82
|
-
"""Get a client by ID."""
|
|
83
|
-
return self.clients.get(client_id)
|
|
84
|
-
|
|
85
|
-
async def register_client(self, client_info: OAuthClientInformationFull) -> None:
|
|
86
|
-
"""Register a new OAuth client."""
|
|
87
|
-
self.clients[client_info.client_id] = client_info
|
|
88
|
-
logger.info(f"Registered external OAuth client: {client_info.client_id}")
|
|
89
|
-
|
|
90
|
-
async def authorize(
|
|
91
|
-
self, client: OAuthClientInformationFull, params: AuthorizationParams
|
|
92
|
-
) -> str:
|
|
93
|
-
"""Create authorization URL redirecting to external provider."""
|
|
94
|
-
# Store authorization request
|
|
95
|
-
import secrets
|
|
96
|
-
|
|
97
|
-
state = secrets.token_urlsafe(32)
|
|
98
|
-
|
|
99
|
-
self.codes[state] = ExternalAuthorizationCode(
|
|
100
|
-
code=state,
|
|
101
|
-
scopes=params.scopes or [],
|
|
102
|
-
expires_at=0, # Will be set by external provider
|
|
103
|
-
client_id=client.client_id,
|
|
104
|
-
code_challenge=params.code_challenge,
|
|
105
|
-
redirect_uri=params.redirect_uri,
|
|
106
|
-
redirect_uri_provided_explicitly=params.redirect_uri_provided_explicitly,
|
|
107
|
-
state=params.state,
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
# Build external provider URL
|
|
111
|
-
external_params = {
|
|
112
|
-
"client_id": self.external_client_id,
|
|
113
|
-
"redirect_uri": f"{self.issuer_url}/callback",
|
|
114
|
-
"response_type": "code",
|
|
115
|
-
"state": state,
|
|
116
|
-
"scope": " ".join(params.scopes or []),
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return construct_redirect_uri(self.external_authorize_url, **external_params)
|
|
120
|
-
|
|
121
|
-
async def handle_callback(self, code: str, state: str) -> str:
|
|
122
|
-
"""Handle callback from external provider."""
|
|
123
|
-
# Get original authorization request
|
|
124
|
-
auth_code = self.codes.get(state)
|
|
125
|
-
if not auth_code:
|
|
126
|
-
raise ValueError("Invalid state parameter")
|
|
127
|
-
|
|
128
|
-
# Exchange code with external provider
|
|
129
|
-
token_data = {
|
|
130
|
-
"grant_type": "authorization_code",
|
|
131
|
-
"code": code,
|
|
132
|
-
"redirect_uri": f"{self.issuer_url}/callback",
|
|
133
|
-
"client_id": self.external_client_id,
|
|
134
|
-
"client_secret": self.external_client_secret,
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
response = await self.http_client.post(
|
|
138
|
-
self.external_token_url,
|
|
139
|
-
data=token_data,
|
|
140
|
-
)
|
|
141
|
-
response.raise_for_status()
|
|
142
|
-
external_tokens = response.json()
|
|
143
|
-
|
|
144
|
-
# Store external tokens
|
|
145
|
-
import secrets
|
|
146
|
-
|
|
147
|
-
internal_code = secrets.token_urlsafe(32)
|
|
148
|
-
|
|
149
|
-
self.codes[internal_code] = ExternalAuthorizationCode(
|
|
150
|
-
code=internal_code,
|
|
151
|
-
scopes=auth_code.scopes,
|
|
152
|
-
expires_at=0,
|
|
153
|
-
client_id=auth_code.client_id,
|
|
154
|
-
code_challenge=auth_code.code_challenge,
|
|
155
|
-
redirect_uri=auth_code.redirect_uri,
|
|
156
|
-
redirect_uri_provided_explicitly=auth_code.redirect_uri_provided_explicitly,
|
|
157
|
-
external_code=code,
|
|
158
|
-
state=auth_code.state,
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
self.tokens[internal_code] = external_tokens
|
|
162
|
-
|
|
163
|
-
# Redirect to original client
|
|
164
|
-
return construct_redirect_uri(
|
|
165
|
-
str(auth_code.redirect_uri),
|
|
166
|
-
code=internal_code,
|
|
167
|
-
state=auth_code.state,
|
|
168
|
-
)
|
|
169
|
-
|
|
170
|
-
async def load_authorization_code(
|
|
171
|
-
self, client: OAuthClientInformationFull, authorization_code: str
|
|
172
|
-
) -> Optional[ExternalAuthorizationCode]:
|
|
173
|
-
"""Load an authorization code."""
|
|
174
|
-
code = self.codes.get(authorization_code)
|
|
175
|
-
if code and code.client_id == client.client_id:
|
|
176
|
-
return code
|
|
177
|
-
return None
|
|
178
|
-
|
|
179
|
-
async def exchange_authorization_code(
|
|
180
|
-
self, client: OAuthClientInformationFull, authorization_code: ExternalAuthorizationCode
|
|
181
|
-
) -> OAuthToken:
|
|
182
|
-
"""Exchange authorization code for tokens."""
|
|
183
|
-
# Get stored external tokens
|
|
184
|
-
external_tokens = self.tokens.get(authorization_code.code)
|
|
185
|
-
if not external_tokens:
|
|
186
|
-
raise ValueError("No tokens found for authorization code")
|
|
187
|
-
|
|
188
|
-
# Map external tokens to MCP tokens
|
|
189
|
-
access_token = external_tokens.get("access_token")
|
|
190
|
-
refresh_token = external_tokens.get("refresh_token")
|
|
191
|
-
expires_in = external_tokens.get("expires_in", 3600)
|
|
192
|
-
|
|
193
|
-
# Store the mapping
|
|
194
|
-
self.tokens[access_token] = {
|
|
195
|
-
"client_id": client.client_id,
|
|
196
|
-
"external_token": access_token,
|
|
197
|
-
"scopes": authorization_code.scopes,
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if refresh_token:
|
|
201
|
-
self.tokens[refresh_token] = {
|
|
202
|
-
"client_id": client.client_id,
|
|
203
|
-
"external_token": refresh_token,
|
|
204
|
-
"scopes": authorization_code.scopes,
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
# Clean up authorization code
|
|
208
|
-
del self.codes[authorization_code.code]
|
|
209
|
-
|
|
210
|
-
return OAuthToken(
|
|
211
|
-
access_token=access_token,
|
|
212
|
-
token_type="bearer",
|
|
213
|
-
expires_in=expires_in,
|
|
214
|
-
refresh_token=refresh_token,
|
|
215
|
-
scope=" ".join(authorization_code.scopes) if authorization_code.scopes else None,
|
|
216
|
-
)
|
|
217
|
-
|
|
218
|
-
async def load_refresh_token(
|
|
219
|
-
self, client: OAuthClientInformationFull, refresh_token: str
|
|
220
|
-
) -> Optional[ExternalRefreshToken]:
|
|
221
|
-
"""Load a refresh token."""
|
|
222
|
-
token_info = self.tokens.get(refresh_token)
|
|
223
|
-
if token_info and token_info["client_id"] == client.client_id:
|
|
224
|
-
return ExternalRefreshToken(
|
|
225
|
-
token=refresh_token,
|
|
226
|
-
client_id=client.client_id,
|
|
227
|
-
scopes=token_info["scopes"],
|
|
228
|
-
external_token=token_info.get("external_token"),
|
|
229
|
-
)
|
|
230
|
-
return None
|
|
231
|
-
|
|
232
|
-
async def exchange_refresh_token(
|
|
233
|
-
self,
|
|
234
|
-
client: OAuthClientInformationFull,
|
|
235
|
-
refresh_token: ExternalRefreshToken,
|
|
236
|
-
scopes: list[str],
|
|
237
|
-
) -> OAuthToken:
|
|
238
|
-
"""Exchange refresh token for new tokens."""
|
|
239
|
-
# Exchange with external provider
|
|
240
|
-
token_data = {
|
|
241
|
-
"grant_type": "refresh_token",
|
|
242
|
-
"refresh_token": refresh_token.external_token or refresh_token.token,
|
|
243
|
-
"client_id": self.external_client_id,
|
|
244
|
-
"client_secret": self.external_client_secret,
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
response = await self.http_client.post(
|
|
248
|
-
self.external_token_url,
|
|
249
|
-
data=token_data,
|
|
250
|
-
)
|
|
251
|
-
response.raise_for_status()
|
|
252
|
-
external_tokens = response.json()
|
|
253
|
-
|
|
254
|
-
# Update stored tokens
|
|
255
|
-
new_access_token = external_tokens.get("access_token")
|
|
256
|
-
new_refresh_token = external_tokens.get("refresh_token", refresh_token.token)
|
|
257
|
-
expires_in = external_tokens.get("expires_in", 3600)
|
|
258
|
-
|
|
259
|
-
self.tokens[new_access_token] = {
|
|
260
|
-
"client_id": client.client_id,
|
|
261
|
-
"external_token": new_access_token,
|
|
262
|
-
"scopes": scopes or refresh_token.scopes,
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if new_refresh_token != refresh_token.token:
|
|
266
|
-
self.tokens[new_refresh_token] = {
|
|
267
|
-
"client_id": client.client_id,
|
|
268
|
-
"external_token": new_refresh_token,
|
|
269
|
-
"scopes": scopes or refresh_token.scopes,
|
|
270
|
-
}
|
|
271
|
-
del self.tokens[refresh_token.token]
|
|
272
|
-
|
|
273
|
-
return OAuthToken(
|
|
274
|
-
access_token=new_access_token,
|
|
275
|
-
token_type="bearer",
|
|
276
|
-
expires_in=expires_in,
|
|
277
|
-
refresh_token=new_refresh_token,
|
|
278
|
-
scope=" ".join(scopes or refresh_token.scopes),
|
|
279
|
-
)
|
|
280
|
-
|
|
281
|
-
async def load_access_token(self, token: str) -> Optional[ExternalAccessToken]:
|
|
282
|
-
"""Load and validate an access token."""
|
|
283
|
-
token_info = self.tokens.get(token)
|
|
284
|
-
if token_info:
|
|
285
|
-
return ExternalAccessToken(
|
|
286
|
-
token=token,
|
|
287
|
-
client_id=token_info["client_id"],
|
|
288
|
-
scopes=token_info["scopes"],
|
|
289
|
-
external_token=token_info.get("external_token"),
|
|
290
|
-
)
|
|
291
|
-
return None
|
|
292
|
-
|
|
293
|
-
async def revoke_token(self, token: ExternalAccessToken | ExternalRefreshToken) -> None:
|
|
294
|
-
"""Revoke a token."""
|
|
295
|
-
self.tokens.pop(token.token, None)
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
def create_github_provider() -> ExternalOAuthProvider:
|
|
299
|
-
"""Create an OAuth provider for GitHub integration."""
|
|
300
|
-
return ExternalOAuthProvider(
|
|
301
|
-
issuer_url=os.getenv("FASTMCP_AUTH_ISSUER_URL", "http://localhost:8000"),
|
|
302
|
-
external_provider="github",
|
|
303
|
-
external_client_id=os.getenv("GITHUB_CLIENT_ID", ""),
|
|
304
|
-
external_client_secret=os.getenv("GITHUB_CLIENT_SECRET", ""),
|
|
305
|
-
external_authorize_url="https://github.com/login/oauth/authorize",
|
|
306
|
-
external_token_url="https://github.com/login/oauth/access_token",
|
|
307
|
-
external_userinfo_url="https://api.github.com/user",
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
def create_google_provider() -> ExternalOAuthProvider:
|
|
312
|
-
"""Create an OAuth provider for Google integration."""
|
|
313
|
-
return ExternalOAuthProvider(
|
|
314
|
-
issuer_url=os.getenv("FASTMCP_AUTH_ISSUER_URL", "http://localhost:8000"),
|
|
315
|
-
external_provider="google",
|
|
316
|
-
external_client_id=os.getenv("GOOGLE_CLIENT_ID", ""),
|
|
317
|
-
external_client_secret=os.getenv("GOOGLE_CLIENT_SECRET", ""),
|
|
318
|
-
external_authorize_url="https://accounts.google.com/o/oauth2/v2/auth",
|
|
319
|
-
external_token_url="https://oauth2.googleapis.com/token",
|
|
320
|
-
external_userinfo_url="https://www.googleapis.com/oauth2/v1/userinfo",
|
|
321
|
-
)
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
"""Sync status prompt for Basic Memory MCP server."""
|
|
2
|
-
|
|
3
|
-
from basic_memory.mcp.server import mcp
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
@mcp.prompt(
|
|
7
|
-
description="""Get sync status with recommendations for AI assistants.
|
|
8
|
-
|
|
9
|
-
This prompt provides both current sync status and guidance on how
|
|
10
|
-
AI assistants should respond when sync operations are in progress or completed.
|
|
11
|
-
""",
|
|
12
|
-
)
|
|
13
|
-
async def sync_status_prompt() -> str:
|
|
14
|
-
"""Get sync status with AI assistant guidance.
|
|
15
|
-
Returns:
|
|
16
|
-
Formatted sync status with AI assistant guidance
|
|
17
|
-
"""
|
|
18
|
-
try: # pragma: no cover
|
|
19
|
-
from basic_memory.services.migration_service import migration_manager
|
|
20
|
-
|
|
21
|
-
state = migration_manager.state
|
|
22
|
-
|
|
23
|
-
# Build status report
|
|
24
|
-
lines = [
|
|
25
|
-
"# Basic Memory Sync Status",
|
|
26
|
-
"",
|
|
27
|
-
f"**Current Status**: {state.status.value.replace('_', ' ').title()}",
|
|
28
|
-
f"**System Ready**: {'Yes' if migration_manager.is_ready else 'No'}",
|
|
29
|
-
"",
|
|
30
|
-
]
|
|
31
|
-
|
|
32
|
-
if migration_manager.is_ready:
|
|
33
|
-
lines.extend(
|
|
34
|
-
[
|
|
35
|
-
"✅ **All sync operations completed** - System is fully operational",
|
|
36
|
-
"",
|
|
37
|
-
"All Basic Memory tools are available and functioning normally.",
|
|
38
|
-
"File indexing is complete and knowledge graphs are up to date.",
|
|
39
|
-
"You can proceed with any knowledge management tasks.",
|
|
40
|
-
]
|
|
41
|
-
)
|
|
42
|
-
else:
|
|
43
|
-
lines.append(f"**Status Message**: {state.message}")
|
|
44
|
-
|
|
45
|
-
if state.status.value == "in_progress":
|
|
46
|
-
if state.projects_total > 0:
|
|
47
|
-
progress = f" ({state.projects_migrated}/{state.projects_total})"
|
|
48
|
-
lines.append(f"**Progress**: {progress}")
|
|
49
|
-
|
|
50
|
-
lines.extend(
|
|
51
|
-
[
|
|
52
|
-
"",
|
|
53
|
-
"🔄 **File synchronization in progress** - Processing files and building knowledge graphs",
|
|
54
|
-
"",
|
|
55
|
-
"**Impact**: Some tools may show status messages instead of normal responses",
|
|
56
|
-
"until sync completes (usually 1-3 minutes).",
|
|
57
|
-
]
|
|
58
|
-
)
|
|
59
|
-
|
|
60
|
-
elif state.status.value == "failed":
|
|
61
|
-
lines.extend(
|
|
62
|
-
[
|
|
63
|
-
"",
|
|
64
|
-
f"❌ **Sync failed**: {state.error or 'Unknown error'}",
|
|
65
|
-
"",
|
|
66
|
-
"**Impact**: System may have limited functionality until issue is resolved.",
|
|
67
|
-
]
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
# Add AI assistant recommendations
|
|
71
|
-
if not migration_manager.is_ready:
|
|
72
|
-
lines.extend(
|
|
73
|
-
[
|
|
74
|
-
"",
|
|
75
|
-
"---",
|
|
76
|
-
"",
|
|
77
|
-
"## AI Assistant Recommendations",
|
|
78
|
-
"",
|
|
79
|
-
"**When sync is in progress:**",
|
|
80
|
-
"- Inform the user about the background file processing",
|
|
81
|
-
"- Suggest using `sync_status()` tool to check progress",
|
|
82
|
-
"- Explain that tools will work normally once sync completes",
|
|
83
|
-
"- Avoid creating complex workflows until sync is done",
|
|
84
|
-
"",
|
|
85
|
-
"**What to tell users:**",
|
|
86
|
-
"- 'Basic Memory is processing your files and building knowledge graphs'",
|
|
87
|
-
"- 'This usually takes 1-3 minutes depending on your content size'",
|
|
88
|
-
"- 'You can check progress anytime with the sync_status tool'",
|
|
89
|
-
"- 'Full functionality will be available once processing completes'",
|
|
90
|
-
"",
|
|
91
|
-
"**User-friendly language:**",
|
|
92
|
-
"- Say 'processing files' instead of 'migration' or 'sync'",
|
|
93
|
-
"- Say 'building knowledge graphs' instead of 'indexing'",
|
|
94
|
-
"- Say 'setting up your knowledge base' instead of 'running migrations'",
|
|
95
|
-
]
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
return "\n".join(lines)
|
|
99
|
-
|
|
100
|
-
except Exception as e: # pragma: no cover
|
|
101
|
-
return f"""# Sync Status - Error
|
|
102
|
-
|
|
103
|
-
❌ **Unable to check sync status**: {str(e)}
|
|
104
|
-
|
|
105
|
-
## AI Assistant Recommendations
|
|
106
|
-
|
|
107
|
-
**When status is unavailable:**
|
|
108
|
-
- Assume the system is likely working normally
|
|
109
|
-
- Try proceeding with normal operations
|
|
110
|
-
- If users report issues, suggest checking logs or restarting
|
|
111
|
-
- Use user-friendly language about 'setting up the knowledge base'
|
|
112
|
-
"""
|