basic-memory 0.14.3__py3-none-any.whl → 0.15.0__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/versions/a1b2c3d4e5f6_fix_project_foreign_keys.py +49 -0
- basic_memory/api/app.py +10 -4
- basic_memory/api/routers/knowledge_router.py +25 -8
- basic_memory/api/routers/project_router.py +99 -4
- basic_memory/api/routers/resource_router.py +3 -3
- basic_memory/cli/app.py +9 -28
- basic_memory/cli/auth.py +277 -0
- basic_memory/cli/commands/cloud/__init__.py +5 -0
- basic_memory/cli/commands/cloud/api_client.py +112 -0
- basic_memory/cli/commands/cloud/bisync_commands.py +818 -0
- basic_memory/cli/commands/cloud/core_commands.py +288 -0
- basic_memory/cli/commands/cloud/mount_commands.py +295 -0
- basic_memory/cli/commands/cloud/rclone_config.py +288 -0
- basic_memory/cli/commands/cloud/rclone_installer.py +198 -0
- basic_memory/cli/commands/command_utils.py +60 -0
- basic_memory/cli/commands/import_memory_json.py +0 -4
- basic_memory/cli/commands/mcp.py +16 -4
- basic_memory/cli/commands/project.py +141 -145
- basic_memory/cli/commands/status.py +34 -22
- basic_memory/cli/commands/sync.py +45 -228
- basic_memory/cli/commands/tool.py +87 -16
- basic_memory/cli/main.py +1 -0
- basic_memory/config.py +96 -20
- basic_memory/db.py +104 -3
- basic_memory/deps.py +20 -3
- basic_memory/file_utils.py +89 -0
- basic_memory/ignore_utils.py +295 -0
- basic_memory/importers/chatgpt_importer.py +1 -1
- 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 +39 -21
- basic_memory/markdown/utils.py +1 -1
- basic_memory/mcp/async_client.py +22 -10
- basic_memory/mcp/project_context.py +141 -0
- basic_memory/mcp/prompts/ai_assistant_guide.py +49 -4
- basic_memory/mcp/prompts/continue_conversation.py +1 -1
- basic_memory/mcp/prompts/recent_activity.py +116 -32
- basic_memory/mcp/prompts/search.py +1 -1
- basic_memory/mcp/prompts/utils.py +11 -4
- basic_memory/mcp/resources/ai_assistant_guide.md +179 -41
- basic_memory/mcp/resources/project_info.py +20 -6
- basic_memory/mcp/server.py +0 -37
- basic_memory/mcp/tools/__init__.py +5 -6
- basic_memory/mcp/tools/build_context.py +39 -19
- basic_memory/mcp/tools/canvas.py +19 -8
- basic_memory/mcp/tools/chatgpt_tools.py +178 -0
- basic_memory/mcp/tools/delete_note.py +67 -34
- basic_memory/mcp/tools/edit_note.py +55 -39
- basic_memory/mcp/tools/headers.py +44 -0
- basic_memory/mcp/tools/list_directory.py +18 -8
- basic_memory/mcp/tools/move_note.py +119 -41
- basic_memory/mcp/tools/project_management.py +77 -229
- basic_memory/mcp/tools/read_content.py +28 -12
- basic_memory/mcp/tools/read_note.py +97 -57
- basic_memory/mcp/tools/recent_activity.py +441 -42
- basic_memory/mcp/tools/search.py +82 -70
- basic_memory/mcp/tools/sync_status.py +5 -4
- basic_memory/mcp/tools/utils.py +19 -0
- basic_memory/mcp/tools/view_note.py +31 -6
- basic_memory/mcp/tools/write_note.py +65 -14
- basic_memory/models/knowledge.py +19 -2
- basic_memory/models/project.py +6 -2
- basic_memory/repository/entity_repository.py +31 -84
- basic_memory/repository/project_repository.py +1 -1
- basic_memory/repository/relation_repository.py +13 -0
- basic_memory/repository/repository.py +2 -2
- basic_memory/repository/search_repository.py +9 -3
- basic_memory/schemas/__init__.py +6 -0
- basic_memory/schemas/base.py +70 -12
- basic_memory/schemas/cloud.py +46 -0
- basic_memory/schemas/memory.py +99 -18
- basic_memory/schemas/project_info.py +9 -10
- basic_memory/schemas/sync_report.py +48 -0
- basic_memory/services/context_service.py +35 -11
- basic_memory/services/directory_service.py +7 -0
- basic_memory/services/entity_service.py +82 -52
- basic_memory/services/initialization.py +30 -11
- basic_memory/services/project_service.py +23 -33
- basic_memory/sync/sync_service.py +148 -24
- basic_memory/sync/watch_service.py +128 -44
- basic_memory/utils.py +181 -109
- {basic_memory-0.14.3.dist-info → basic_memory-0.15.0.dist-info}/METADATA +26 -96
- basic_memory-0.15.0.dist-info/RECORD +147 -0
- basic_memory/mcp/project_session.py +0 -120
- basic_memory-0.14.3.dist-info/RECORD +0 -132
- {basic_memory-0.14.3.dist-info → basic_memory-0.15.0.dist-info}/WHEEL +0 -0
- {basic_memory-0.14.3.dist-info → basic_memory-0.15.0.dist-info}/entry_points.txt +0 -0
- {basic_memory-0.14.3.dist-info → basic_memory-0.15.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -9,13 +9,13 @@ from rich.console import Console
|
|
|
9
9
|
from rich.table import Table
|
|
10
10
|
|
|
11
11
|
from basic_memory.cli.app import app
|
|
12
|
-
from basic_memory.
|
|
13
|
-
from basic_memory.
|
|
12
|
+
from basic_memory.cli.commands.cloud import get_authenticated_headers
|
|
13
|
+
from basic_memory.cli.commands.command_utils import get_project_info
|
|
14
|
+
from basic_memory.config import ConfigManager
|
|
14
15
|
import json
|
|
15
16
|
from datetime import datetime
|
|
16
17
|
|
|
17
18
|
from rich.panel import Panel
|
|
18
|
-
from rich.tree import Tree
|
|
19
19
|
from basic_memory.mcp.async_client import client
|
|
20
20
|
from basic_memory.mcp.tools.utils import call_get
|
|
21
21
|
from basic_memory.schemas.project_info import ProjectList
|
|
@@ -23,8 +23,8 @@ from basic_memory.mcp.tools.utils import call_post
|
|
|
23
23
|
from basic_memory.schemas.project_info import ProjectStatusResponse
|
|
24
24
|
from basic_memory.mcp.tools.utils import call_delete
|
|
25
25
|
from basic_memory.mcp.tools.utils import call_put
|
|
26
|
-
from basic_memory.mcp.tools.utils import call_patch
|
|
27
26
|
from basic_memory.utils import generate_permalink
|
|
27
|
+
from basic_memory.mcp.tools.utils import call_patch
|
|
28
28
|
|
|
29
29
|
console = Console()
|
|
30
30
|
|
|
@@ -32,6 +32,8 @@ console = Console()
|
|
|
32
32
|
project_app = typer.Typer(help="Manage multiple Basic Memory projects")
|
|
33
33
|
app.add_typer(project_app, name="project")
|
|
34
34
|
|
|
35
|
+
config = ConfigManager().config
|
|
36
|
+
|
|
35
37
|
|
|
36
38
|
def format_path(path: str) -> str:
|
|
37
39
|
"""Format a path for display, using ~ for home directory."""
|
|
@@ -43,22 +45,24 @@ def format_path(path: str) -> str:
|
|
|
43
45
|
|
|
44
46
|
@project_app.command("list")
|
|
45
47
|
def list_projects() -> None:
|
|
46
|
-
"""List all
|
|
48
|
+
"""List all Basic Memory projects."""
|
|
47
49
|
# Use API to list projects
|
|
48
50
|
try:
|
|
49
|
-
|
|
51
|
+
auth_headers = {}
|
|
52
|
+
if config.cloud_mode_enabled:
|
|
53
|
+
auth_headers = asyncio.run(get_authenticated_headers())
|
|
54
|
+
|
|
55
|
+
response = asyncio.run(call_get(client, "/projects/projects", headers=auth_headers))
|
|
50
56
|
result = ProjectList.model_validate(response.json())
|
|
51
57
|
|
|
52
58
|
table = Table(title="Basic Memory Projects")
|
|
53
59
|
table.add_column("Name", style="cyan")
|
|
54
60
|
table.add_column("Path", style="green")
|
|
55
|
-
table.add_column("Default", style="
|
|
56
|
-
table.add_column("Active", style="magenta")
|
|
61
|
+
table.add_column("Default", style="magenta")
|
|
57
62
|
|
|
58
63
|
for project in result.projects:
|
|
59
64
|
is_default = "✓" if project.is_default else ""
|
|
60
|
-
|
|
61
|
-
table.add_row(project.name, format_path(project.path), is_default, is_active)
|
|
65
|
+
table.add_row(project.name, format_path(project.path), is_default)
|
|
62
66
|
|
|
63
67
|
console.print(table)
|
|
64
68
|
except Exception as e:
|
|
@@ -66,42 +70,75 @@ def list_projects() -> None:
|
|
|
66
70
|
raise typer.Exit(1)
|
|
67
71
|
|
|
68
72
|
|
|
69
|
-
|
|
70
|
-
def add_project(
|
|
71
|
-
name: str = typer.Argument(..., help="Name of the project"),
|
|
72
|
-
path: str = typer.Argument(..., help="Path to the project directory"),
|
|
73
|
-
set_default: bool = typer.Option(False, "--default", help="Set as default project"),
|
|
74
|
-
) -> None:
|
|
75
|
-
"""Add a new project."""
|
|
76
|
-
# Resolve to absolute path
|
|
77
|
-
resolved_path = os.path.abspath(os.path.expanduser(path))
|
|
73
|
+
if config.cloud_mode_enabled:
|
|
78
74
|
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
@project_app.command("add")
|
|
76
|
+
def add_project_cloud(
|
|
77
|
+
name: str = typer.Argument(..., help="Name of the project"),
|
|
78
|
+
set_default: bool = typer.Option(False, "--default", help="Set as default project"),
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Add a new project to Basic Memory Cloud"""
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
try:
|
|
83
|
+
auth_headers = asyncio.run(get_authenticated_headers())
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
85
|
+
data = {"name": name, "path": generate_permalink(name), "set_default": set_default}
|
|
86
|
+
|
|
87
|
+
response = asyncio.run(
|
|
88
|
+
call_post(client, "/projects/projects", json=data, headers=auth_headers)
|
|
89
|
+
)
|
|
90
|
+
result = ProjectStatusResponse.model_validate(response.json())
|
|
91
|
+
|
|
92
|
+
console.print(f"[green]{result.message}[/green]")
|
|
93
|
+
except Exception as e:
|
|
94
|
+
console.print(f"[red]Error adding project: {str(e)}[/red]")
|
|
95
|
+
raise typer.Exit(1)
|
|
96
|
+
|
|
97
|
+
# Display usage hint
|
|
98
|
+
console.print("\nTo use this project:")
|
|
99
|
+
console.print(f" basic-memory --project={name} <command>")
|
|
100
|
+
else:
|
|
101
|
+
|
|
102
|
+
@project_app.command("add")
|
|
103
|
+
def add_project(
|
|
104
|
+
name: str = typer.Argument(..., help="Name of the project"),
|
|
105
|
+
path: str = typer.Argument(..., help="Path to the project directory"),
|
|
106
|
+
set_default: bool = typer.Option(False, "--default", help="Set as default project"),
|
|
107
|
+
) -> None:
|
|
108
|
+
"""Add a new project."""
|
|
109
|
+
# Resolve to absolute path
|
|
110
|
+
resolved_path = Path(os.path.abspath(os.path.expanduser(path))).as_posix()
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
data = {"name": name, "path": resolved_path, "set_default": set_default}
|
|
114
|
+
|
|
115
|
+
response = asyncio.run(call_post(client, "/projects/projects", json=data))
|
|
116
|
+
result = ProjectStatusResponse.model_validate(response.json())
|
|
89
117
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
118
|
+
console.print(f"[green]{result.message}[/green]")
|
|
119
|
+
except Exception as e:
|
|
120
|
+
console.print(f"[red]Error adding project: {str(e)}[/red]")
|
|
121
|
+
raise typer.Exit(1)
|
|
122
|
+
|
|
123
|
+
# Display usage hint
|
|
124
|
+
console.print("\nTo use this project:")
|
|
125
|
+
console.print(f" basic-memory --project={name} <command>")
|
|
95
126
|
|
|
96
127
|
|
|
97
128
|
@project_app.command("remove")
|
|
98
129
|
def remove_project(
|
|
99
130
|
name: str = typer.Argument(..., help="Name of the project to remove"),
|
|
100
131
|
) -> None:
|
|
101
|
-
"""Remove a project
|
|
132
|
+
"""Remove a project."""
|
|
102
133
|
try:
|
|
103
|
-
|
|
104
|
-
|
|
134
|
+
auth_headers = {}
|
|
135
|
+
if config.cloud_mode_enabled:
|
|
136
|
+
auth_headers = asyncio.run(get_authenticated_headers())
|
|
137
|
+
|
|
138
|
+
project_permalink = generate_permalink(name)
|
|
139
|
+
response = asyncio.run(
|
|
140
|
+
call_delete(client, f"/projects/{project_permalink}", headers=auth_headers)
|
|
141
|
+
)
|
|
105
142
|
result = ProjectStatusResponse.model_validate(response.json())
|
|
106
143
|
|
|
107
144
|
console.print(f"[green]{result.message}[/green]")
|
|
@@ -113,101 +150,96 @@ def remove_project(
|
|
|
113
150
|
console.print("[yellow]Note: The project files have not been deleted from disk.[/yellow]")
|
|
114
151
|
|
|
115
152
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
response = asyncio.run(
|
|
167
|
-
call_patch(client, f"/{current_project}/project/{project_name}", json=data)
|
|
168
|
-
)
|
|
169
|
-
result = ProjectStatusResponse.model_validate(response.json())
|
|
153
|
+
if not config.cloud_mode_enabled:
|
|
154
|
+
|
|
155
|
+
@project_app.command("default")
|
|
156
|
+
def set_default_project(
|
|
157
|
+
name: str = typer.Argument(..., help="Name of the project to set as CLI default"),
|
|
158
|
+
) -> None:
|
|
159
|
+
"""Set the default project when 'config.default_project_mode' is set."""
|
|
160
|
+
try:
|
|
161
|
+
project_permalink = generate_permalink(name)
|
|
162
|
+
response = asyncio.run(call_put(client, f"/projects/{project_permalink}/default"))
|
|
163
|
+
result = ProjectStatusResponse.model_validate(response.json())
|
|
164
|
+
|
|
165
|
+
console.print(f"[green]{result.message}[/green]")
|
|
166
|
+
except Exception as e:
|
|
167
|
+
console.print(f"[red]Error setting default project: {str(e)}[/red]")
|
|
168
|
+
raise typer.Exit(1)
|
|
169
|
+
|
|
170
|
+
@project_app.command("sync-config")
|
|
171
|
+
def synchronize_projects() -> None:
|
|
172
|
+
"""Synchronize project config between configuration file and database."""
|
|
173
|
+
# Call the API to synchronize projects
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
response = asyncio.run(call_post(client, "/projects/config/sync"))
|
|
177
|
+
result = ProjectStatusResponse.model_validate(response.json())
|
|
178
|
+
|
|
179
|
+
console.print(f"[green]{result.message}[/green]")
|
|
180
|
+
except Exception as e: # pragma: no cover
|
|
181
|
+
console.print(f"[red]Error synchronizing projects: {str(e)}[/red]")
|
|
182
|
+
raise typer.Exit(1)
|
|
183
|
+
|
|
184
|
+
@project_app.command("move")
|
|
185
|
+
def move_project(
|
|
186
|
+
name: str = typer.Argument(..., help="Name of the project to move"),
|
|
187
|
+
new_path: str = typer.Argument(..., help="New absolute path for the project"),
|
|
188
|
+
) -> None:
|
|
189
|
+
"""Move a project to a new location."""
|
|
190
|
+
# Resolve to absolute path
|
|
191
|
+
resolved_path = Path(os.path.abspath(os.path.expanduser(new_path))).as_posix()
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
data = {"path": resolved_path}
|
|
195
|
+
|
|
196
|
+
project_permalink = generate_permalink(name)
|
|
197
|
+
|
|
198
|
+
# TODO fix route to use ProjectPathDep
|
|
199
|
+
response = asyncio.run(
|
|
200
|
+
call_patch(client, f"/{name}/project/{project_permalink}", json=data)
|
|
201
|
+
)
|
|
202
|
+
result = ProjectStatusResponse.model_validate(response.json())
|
|
170
203
|
|
|
171
|
-
|
|
204
|
+
console.print(f"[green]{result.message}[/green]")
|
|
172
205
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
206
|
+
# Show important file movement reminder
|
|
207
|
+
console.print() # Empty line for spacing
|
|
208
|
+
console.print(
|
|
209
|
+
Panel(
|
|
210
|
+
"[bold red]IMPORTANT:[/bold red] Project configuration updated successfully.\n\n"
|
|
211
|
+
"[yellow]You must manually move your project files from the old location to:[/yellow]\n"
|
|
212
|
+
f"[cyan]{resolved_path}[/cyan]\n\n"
|
|
213
|
+
"[dim]Basic Memory has only updated the configuration - your files remain in their original location.[/dim]",
|
|
214
|
+
title="⚠️ Manual File Movement Required",
|
|
215
|
+
border_style="yellow",
|
|
216
|
+
expand=False,
|
|
217
|
+
)
|
|
184
218
|
)
|
|
185
|
-
)
|
|
186
219
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
220
|
+
except Exception as e:
|
|
221
|
+
console.print(f"[red]Error moving project: {str(e)}[/red]")
|
|
222
|
+
raise typer.Exit(1)
|
|
190
223
|
|
|
191
224
|
|
|
192
225
|
@project_app.command("info")
|
|
193
226
|
def display_project_info(
|
|
227
|
+
name: str = typer.Argument(..., help="Name of the project"),
|
|
194
228
|
json_output: bool = typer.Option(False, "--json", help="Output in JSON format"),
|
|
195
229
|
):
|
|
196
230
|
"""Display detailed information and statistics about the current project."""
|
|
197
231
|
try:
|
|
198
232
|
# Get project info
|
|
199
|
-
info = asyncio.run(
|
|
233
|
+
info = asyncio.run(get_project_info(name))
|
|
200
234
|
|
|
201
235
|
if json_output:
|
|
202
236
|
# Convert to JSON and print
|
|
203
237
|
print(json.dumps(info.model_dump(), indent=2, default=str))
|
|
204
238
|
else:
|
|
205
|
-
# Create rich display
|
|
206
|
-
console = Console()
|
|
207
|
-
|
|
208
239
|
# Project configuration section
|
|
209
240
|
console.print(
|
|
210
241
|
Panel(
|
|
242
|
+
f"Basic Memory version: [bold green]{info.system.version}[/bold green]\n"
|
|
211
243
|
f"[bold]Project:[/bold] {info.project_name}\n"
|
|
212
244
|
f"[bold]Path:[/bold] {info.project_path}\n"
|
|
213
245
|
f"[bold]Default Project:[/bold] {info.default_project}\n",
|
|
@@ -277,42 +309,6 @@ def display_project_info(
|
|
|
277
309
|
|
|
278
310
|
console.print(recent_table)
|
|
279
311
|
|
|
280
|
-
# System status
|
|
281
|
-
system_tree = Tree("🖥️ System Status")
|
|
282
|
-
system_tree.add(f"Basic Memory version: [bold green]{info.system.version}[/bold green]")
|
|
283
|
-
system_tree.add(
|
|
284
|
-
f"Database: [cyan]{info.system.database_path}[/cyan] ([green]{info.system.database_size}[/green])"
|
|
285
|
-
)
|
|
286
|
-
|
|
287
|
-
# Watch status
|
|
288
|
-
if info.system.watch_status: # pragma: no cover
|
|
289
|
-
watch_branch = system_tree.add("Watch Service")
|
|
290
|
-
running = info.system.watch_status.get("running", False)
|
|
291
|
-
status_color = "green" if running else "red"
|
|
292
|
-
watch_branch.add(
|
|
293
|
-
f"Status: [bold {status_color}]{'Running' if running else 'Stopped'}[/bold {status_color}]"
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
if running:
|
|
297
|
-
start_time = (
|
|
298
|
-
datetime.fromisoformat(info.system.watch_status.get("start_time", ""))
|
|
299
|
-
if isinstance(info.system.watch_status.get("start_time"), str)
|
|
300
|
-
else info.system.watch_status.get("start_time")
|
|
301
|
-
)
|
|
302
|
-
watch_branch.add(
|
|
303
|
-
f"Running since: [cyan]{start_time.strftime('%Y-%m-%d %H:%M')}[/cyan]"
|
|
304
|
-
)
|
|
305
|
-
watch_branch.add(
|
|
306
|
-
f"Files synced: [green]{info.system.watch_status.get('synced_files', 0)}[/green]"
|
|
307
|
-
)
|
|
308
|
-
watch_branch.add(
|
|
309
|
-
f"Errors: [{'red' if info.system.watch_status.get('error_count', 0) > 0 else 'green'}]{info.system.watch_status.get('error_count', 0)}[/{'red' if info.system.watch_status.get('error_count', 0) > 0 else 'green'}]"
|
|
310
|
-
)
|
|
311
|
-
else:
|
|
312
|
-
system_tree.add("[yellow]Watch service not running[/yellow]")
|
|
313
|
-
|
|
314
|
-
console.print(system_tree)
|
|
315
|
-
|
|
316
312
|
# Available projects
|
|
317
313
|
projects_table = Table(title="📁 Available Projects")
|
|
318
314
|
projects_table.add_column("Name", style="blue")
|
|
@@ -2,19 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
from typing import Set, Dict
|
|
5
|
+
from typing import Annotated, Optional
|
|
5
6
|
|
|
7
|
+
from mcp.server.fastmcp.exceptions import ToolError
|
|
6
8
|
import typer
|
|
7
9
|
from loguru import logger
|
|
8
10
|
from rich.console import Console
|
|
9
11
|
from rich.panel import Panel
|
|
10
12
|
from rich.tree import Tree
|
|
11
13
|
|
|
12
|
-
from basic_memory import db
|
|
13
14
|
from basic_memory.cli.app import app
|
|
14
|
-
from basic_memory.cli.commands.
|
|
15
|
-
from basic_memory.
|
|
16
|
-
from basic_memory.
|
|
17
|
-
from basic_memory.
|
|
15
|
+
from basic_memory.cli.commands.cloud import get_authenticated_headers
|
|
16
|
+
from basic_memory.mcp.async_client import client
|
|
17
|
+
from basic_memory.mcp.tools.utils import call_post
|
|
18
|
+
from basic_memory.schemas import SyncReportResponse
|
|
19
|
+
from basic_memory.mcp.project_context import get_active_project
|
|
18
20
|
|
|
19
21
|
# Create rich console
|
|
20
22
|
console = Console()
|
|
@@ -47,7 +49,7 @@ def add_files_to_tree(
|
|
|
47
49
|
branch.add(f"[{style}]{file_name}[/{style}]")
|
|
48
50
|
|
|
49
51
|
|
|
50
|
-
def group_changes_by_directory(changes:
|
|
52
|
+
def group_changes_by_directory(changes: SyncReportResponse) -> Dict[str, Dict[str, int]]:
|
|
51
53
|
"""Group changes by directory for summary view."""
|
|
52
54
|
by_dir = {}
|
|
53
55
|
for change_type, paths in [
|
|
@@ -87,7 +89,9 @@ def build_directory_summary(counts: Dict[str, int]) -> str:
|
|
|
87
89
|
return " ".join(parts)
|
|
88
90
|
|
|
89
91
|
|
|
90
|
-
def display_changes(
|
|
92
|
+
def display_changes(
|
|
93
|
+
project_name: str, title: str, changes: SyncReportResponse, verbose: bool = False
|
|
94
|
+
):
|
|
91
95
|
"""Display changes using Rich for better visualization."""
|
|
92
96
|
tree = Tree(f"{project_name}: {title}")
|
|
93
97
|
|
|
@@ -122,33 +126,41 @@ def display_changes(project_name: str, title: str, changes: SyncReport, verbose:
|
|
|
122
126
|
console.print(Panel(tree, expand=False))
|
|
123
127
|
|
|
124
128
|
|
|
125
|
-
async def run_status(verbose: bool = False): # pragma: no cover
|
|
129
|
+
async def run_status(project: Optional[str] = None, verbose: bool = False): # pragma: no cover
|
|
126
130
|
"""Check sync status of files vs database."""
|
|
127
|
-
# Check knowledge/ directory
|
|
128
131
|
|
|
129
|
-
|
|
130
|
-
|
|
132
|
+
try:
|
|
133
|
+
from basic_memory.config import ConfigManager
|
|
134
|
+
|
|
135
|
+
config = ConfigManager().config
|
|
136
|
+
auth_headers = {}
|
|
137
|
+
if config.cloud_mode_enabled:
|
|
138
|
+
auth_headers = await get_authenticated_headers()
|
|
139
|
+
|
|
140
|
+
project_item = await get_active_project(client, project, None)
|
|
141
|
+
response = await call_post(
|
|
142
|
+
client, f"{project_item.project_url}/project/status", headers=auth_headers
|
|
143
|
+
)
|
|
144
|
+
sync_report = SyncReportResponse.model_validate(response.json())
|
|
131
145
|
|
|
132
|
-
|
|
133
|
-
db_path=app_config.database_path, db_type=db.DatabaseType.FILESYSTEM
|
|
134
|
-
)
|
|
135
|
-
project_repository = ProjectRepository(session_maker)
|
|
136
|
-
project = await project_repository.get_by_name(config.project)
|
|
137
|
-
if not project: # pragma: no cover
|
|
138
|
-
raise Exception(f"Project '{config.project}' not found")
|
|
146
|
+
display_changes(project_item.name, "Status", sync_report, verbose)
|
|
139
147
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
148
|
+
except (ValueError, ToolError) as e:
|
|
149
|
+
console.print(f"[red]✗ Error: {e}[/red]")
|
|
150
|
+
raise typer.Exit(1)
|
|
143
151
|
|
|
144
152
|
|
|
145
153
|
@app.command()
|
|
146
154
|
def status(
|
|
155
|
+
project: Annotated[
|
|
156
|
+
Optional[str],
|
|
157
|
+
typer.Option(help="The project name."),
|
|
158
|
+
] = None,
|
|
147
159
|
verbose: bool = typer.Option(False, "--verbose", "-v", help="Show detailed file information"),
|
|
148
160
|
):
|
|
149
161
|
"""Show sync status between files and database."""
|
|
150
162
|
try:
|
|
151
|
-
asyncio.run(run_status(verbose)) # pragma: no cover
|
|
163
|
+
asyncio.run(run_status(project, verbose)) # pragma: no cover
|
|
152
164
|
except Exception as e:
|
|
153
165
|
logger.error(f"Error checking status: {e}")
|
|
154
166
|
typer.echo(f"Error checking status: {e}", err=True)
|