basic-memory 0.14.4__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.

Files changed (82) hide show
  1. basic_memory/__init__.py +1 -1
  2. basic_memory/alembic/versions/a1b2c3d4e5f6_fix_project_foreign_keys.py +5 -9
  3. basic_memory/api/app.py +10 -4
  4. basic_memory/api/routers/knowledge_router.py +25 -8
  5. basic_memory/api/routers/project_router.py +99 -4
  6. basic_memory/cli/app.py +9 -28
  7. basic_memory/cli/auth.py +277 -0
  8. basic_memory/cli/commands/cloud/__init__.py +5 -0
  9. basic_memory/cli/commands/cloud/api_client.py +112 -0
  10. basic_memory/cli/commands/cloud/bisync_commands.py +818 -0
  11. basic_memory/cli/commands/cloud/core_commands.py +288 -0
  12. basic_memory/cli/commands/cloud/mount_commands.py +295 -0
  13. basic_memory/cli/commands/cloud/rclone_config.py +288 -0
  14. basic_memory/cli/commands/cloud/rclone_installer.py +198 -0
  15. basic_memory/cli/commands/command_utils.py +60 -0
  16. basic_memory/cli/commands/import_memory_json.py +0 -4
  17. basic_memory/cli/commands/mcp.py +16 -4
  18. basic_memory/cli/commands/project.py +139 -142
  19. basic_memory/cli/commands/status.py +34 -22
  20. basic_memory/cli/commands/sync.py +45 -228
  21. basic_memory/cli/commands/tool.py +87 -16
  22. basic_memory/cli/main.py +1 -0
  23. basic_memory/config.py +76 -12
  24. basic_memory/db.py +104 -3
  25. basic_memory/deps.py +20 -3
  26. basic_memory/file_utils.py +37 -13
  27. basic_memory/ignore_utils.py +295 -0
  28. basic_memory/markdown/plugins.py +9 -7
  29. basic_memory/mcp/async_client.py +22 -10
  30. basic_memory/mcp/project_context.py +141 -0
  31. basic_memory/mcp/prompts/ai_assistant_guide.py +49 -4
  32. basic_memory/mcp/prompts/continue_conversation.py +1 -1
  33. basic_memory/mcp/prompts/recent_activity.py +116 -32
  34. basic_memory/mcp/prompts/search.py +1 -1
  35. basic_memory/mcp/prompts/utils.py +11 -4
  36. basic_memory/mcp/resources/ai_assistant_guide.md +179 -41
  37. basic_memory/mcp/resources/project_info.py +20 -6
  38. basic_memory/mcp/server.py +0 -37
  39. basic_memory/mcp/tools/__init__.py +5 -6
  40. basic_memory/mcp/tools/build_context.py +29 -19
  41. basic_memory/mcp/tools/canvas.py +19 -8
  42. basic_memory/mcp/tools/chatgpt_tools.py +178 -0
  43. basic_memory/mcp/tools/delete_note.py +67 -34
  44. basic_memory/mcp/tools/edit_note.py +55 -39
  45. basic_memory/mcp/tools/headers.py +44 -0
  46. basic_memory/mcp/tools/list_directory.py +18 -8
  47. basic_memory/mcp/tools/move_note.py +119 -41
  48. basic_memory/mcp/tools/project_management.py +61 -228
  49. basic_memory/mcp/tools/read_content.py +28 -12
  50. basic_memory/mcp/tools/read_note.py +83 -46
  51. basic_memory/mcp/tools/recent_activity.py +441 -42
  52. basic_memory/mcp/tools/search.py +82 -70
  53. basic_memory/mcp/tools/sync_status.py +5 -4
  54. basic_memory/mcp/tools/utils.py +19 -0
  55. basic_memory/mcp/tools/view_note.py +31 -6
  56. basic_memory/mcp/tools/write_note.py +65 -14
  57. basic_memory/models/knowledge.py +12 -6
  58. basic_memory/models/project.py +6 -2
  59. basic_memory/repository/entity_repository.py +29 -82
  60. basic_memory/repository/relation_repository.py +13 -0
  61. basic_memory/repository/repository.py +2 -2
  62. basic_memory/repository/search_repository.py +4 -2
  63. basic_memory/schemas/__init__.py +6 -0
  64. basic_memory/schemas/base.py +39 -11
  65. basic_memory/schemas/cloud.py +46 -0
  66. basic_memory/schemas/memory.py +90 -21
  67. basic_memory/schemas/project_info.py +9 -10
  68. basic_memory/schemas/sync_report.py +48 -0
  69. basic_memory/services/context_service.py +25 -11
  70. basic_memory/services/entity_service.py +75 -45
  71. basic_memory/services/initialization.py +30 -11
  72. basic_memory/services/project_service.py +13 -23
  73. basic_memory/sync/sync_service.py +145 -21
  74. basic_memory/sync/watch_service.py +101 -40
  75. basic_memory/utils.py +14 -4
  76. {basic_memory-0.14.4.dist-info → basic_memory-0.15.0.dist-info}/METADATA +7 -6
  77. basic_memory-0.15.0.dist-info/RECORD +147 -0
  78. basic_memory/mcp/project_session.py +0 -120
  79. basic_memory-0.14.4.dist-info/RECORD +0 -133
  80. {basic_memory-0.14.4.dist-info → basic_memory-0.15.0.dist-info}/WHEEL +0 -0
  81. {basic_memory-0.14.4.dist-info → basic_memory-0.15.0.dist-info}/entry_points.txt +0 -0
  82. {basic_memory-0.14.4.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.mcp.project_session import session
13
- from basic_memory.mcp.resources.project_info import project_info
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
@@ -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 configured projects."""
48
+ """List all Basic Memory projects."""
47
49
  # Use API to list projects
48
50
  try:
49
- response = asyncio.run(call_get(client, "/projects/projects"))
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="yellow")
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
- is_active = "✓" if session.get_current_project() == project.name else ""
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
- @project_app.command("add")
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 = Path(os.path.abspath(os.path.expanduser(path))).as_posix()
73
+ if config.cloud_mode_enabled:
78
74
 
79
- try:
80
- data = {"name": name, "path": resolved_path, "set_default": set_default}
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
- response = asyncio.run(call_post(client, "/projects/projects", json=data))
83
- result = ProjectStatusResponse.model_validate(response.json())
82
+ try:
83
+ auth_headers = asyncio.run(get_authenticated_headers())
84
84
 
85
- console.print(f"[green]{result.message}[/green]")
86
- except Exception as e:
87
- console.print(f"[red]Error adding project: {str(e)}[/red]")
88
- raise typer.Exit(1)
85
+ data = {"name": name, "path": generate_permalink(name), "set_default": set_default}
89
86
 
90
- # Display usage hint
91
- console.print("\nTo use this project:")
92
- console.print(f" basic-memory --project={name} <command>")
93
- console.print(" # or")
94
- console.print(f" basic-memory project default {name}")
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())
117
+
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 from configuration."""
132
+ """Remove a project."""
102
133
  try:
134
+ auth_headers = {}
135
+ if config.cloud_mode_enabled:
136
+ auth_headers = asyncio.run(get_authenticated_headers())
137
+
103
138
  project_permalink = generate_permalink(name)
104
- response = asyncio.run(call_delete(client, f"/projects/{project_permalink}"))
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,100 +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
- @project_app.command("default")
117
- def set_default_project(
118
- name: str = typer.Argument(..., help="Name of the project to set as default"),
119
- ) -> None:
120
- """Set the default project and activate it for the current session."""
121
- try:
122
- project_permalink = generate_permalink(name)
123
- response = asyncio.run(call_put(client, f"/projects/{project_permalink}/default"))
124
- result = ProjectStatusResponse.model_validate(response.json())
125
-
126
- console.print(f"[green]{result.message}[/green]")
127
- except Exception as e:
128
- console.print(f"[red]Error setting default project: {str(e)}[/red]")
129
- raise typer.Exit(1)
130
-
131
- # The API call above should have updated both config and MCP session
132
- # No need for manual reload - the project service handles this automatically
133
- console.print("[green]Project activated for current session[/green]")
134
-
135
-
136
- @project_app.command("sync-config")
137
- def synchronize_projects() -> None:
138
- """Synchronize project config between configuration file and database."""
139
- # Call the API to synchronize projects
140
-
141
- try:
142
- response = asyncio.run(call_post(client, "/projects/sync"))
143
- result = ProjectStatusResponse.model_validate(response.json())
144
-
145
- console.print(f"[green]{result.message}[/green]")
146
- except Exception as e: # pragma: no cover
147
- console.print(f"[red]Error synchronizing projects: {str(e)}[/red]")
148
- raise typer.Exit(1)
149
-
150
-
151
- @project_app.command("move")
152
- def move_project(
153
- name: str = typer.Argument(..., help="Name of the project to move"),
154
- new_path: str = typer.Argument(..., help="New absolute path for the project"),
155
- ) -> None:
156
- """Move a project to a new location."""
157
- # Resolve to absolute path
158
- resolved_path = Path(os.path.abspath(os.path.expanduser(new_path))).as_posix()
159
-
160
- try:
161
- data = {"path": resolved_path}
162
-
163
- project_permalink = generate_permalink(name)
164
- current_project = session.get_current_project()
165
- response = asyncio.run(
166
- call_patch(client, f"/{current_project}/project/{project_permalink}", json=data)
167
- )
168
- 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())
169
203
 
170
- console.print(f"[green]{result.message}[/green]")
204
+ console.print(f"[green]{result.message}[/green]")
171
205
 
172
- # Show important file movement reminder
173
- console.print() # Empty line for spacing
174
- console.print(
175
- Panel(
176
- "[bold red]IMPORTANT:[/bold red] Project configuration updated successfully.\n\n"
177
- "[yellow]You must manually move your project files from the old location to:[/yellow]\n"
178
- f"[cyan]{resolved_path}[/cyan]\n\n"
179
- "[dim]Basic Memory has only updated the configuration - your files remain in their original location.[/dim]",
180
- title="⚠️ Manual File Movement Required",
181
- border_style="yellow",
182
- expand=False,
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
+ )
183
218
  )
184
- )
185
219
 
186
- except Exception as e:
187
- console.print(f"[red]Error moving project: {str(e)}[/red]")
188
- raise typer.Exit(1)
220
+ except Exception as e:
221
+ console.print(f"[red]Error moving project: {str(e)}[/red]")
222
+ raise typer.Exit(1)
189
223
 
190
224
 
191
225
  @project_app.command("info")
192
226
  def display_project_info(
227
+ name: str = typer.Argument(..., help="Name of the project"),
193
228
  json_output: bool = typer.Option(False, "--json", help="Output in JSON format"),
194
229
  ):
195
230
  """Display detailed information and statistics about the current project."""
196
231
  try:
197
232
  # Get project info
198
- info = asyncio.run(project_info.fn()) # type: ignore # pyright: ignore [reportAttributeAccessIssue]
233
+ info = asyncio.run(get_project_info(name))
199
234
 
200
235
  if json_output:
201
236
  # Convert to JSON and print
202
237
  print(json.dumps(info.model_dump(), indent=2, default=str))
203
238
  else:
204
- # Create rich display
205
- console = Console()
206
-
207
239
  # Project configuration section
208
240
  console.print(
209
241
  Panel(
242
+ f"Basic Memory version: [bold green]{info.system.version}[/bold green]\n"
210
243
  f"[bold]Project:[/bold] {info.project_name}\n"
211
244
  f"[bold]Path:[/bold] {info.project_path}\n"
212
245
  f"[bold]Default Project:[/bold] {info.default_project}\n",
@@ -276,42 +309,6 @@ def display_project_info(
276
309
 
277
310
  console.print(recent_table)
278
311
 
279
- # System status
280
- system_tree = Tree("🖥️ System Status")
281
- system_tree.add(f"Basic Memory version: [bold green]{info.system.version}[/bold green]")
282
- system_tree.add(
283
- f"Database: [cyan]{info.system.database_path}[/cyan] ([green]{info.system.database_size}[/green])"
284
- )
285
-
286
- # Watch status
287
- if info.system.watch_status: # pragma: no cover
288
- watch_branch = system_tree.add("Watch Service")
289
- running = info.system.watch_status.get("running", False)
290
- status_color = "green" if running else "red"
291
- watch_branch.add(
292
- f"Status: [bold {status_color}]{'Running' if running else 'Stopped'}[/bold {status_color}]"
293
- )
294
-
295
- if running:
296
- start_time = (
297
- datetime.fromisoformat(info.system.watch_status.get("start_time", ""))
298
- if isinstance(info.system.watch_status.get("start_time"), str)
299
- else info.system.watch_status.get("start_time")
300
- )
301
- watch_branch.add(
302
- f"Running since: [cyan]{start_time.strftime('%Y-%m-%d %H:%M')}[/cyan]"
303
- )
304
- watch_branch.add(
305
- f"Files synced: [green]{info.system.watch_status.get('synced_files', 0)}[/green]"
306
- )
307
- watch_branch.add(
308
- 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'}]"
309
- )
310
- else:
311
- system_tree.add("[yellow]Watch service not running[/yellow]")
312
-
313
- console.print(system_tree)
314
-
315
312
  # Available projects
316
313
  projects_table = Table(title="📁 Available Projects")
317
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.sync import get_sync_service
15
- from basic_memory.config import ConfigManager, get_project_config
16
- from basic_memory.repository import ProjectRepository
17
- from basic_memory.sync.sync_service import SyncReport
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: SyncReport) -> Dict[str, Dict[str, int]]:
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(project_name: str, title: str, changes: SyncReport, verbose: bool = False):
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
- app_config = ConfigManager().config
130
- config = get_project_config()
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
- _, session_maker = await db.get_or_create_db(
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
- sync_service = await get_sync_service(project)
141
- knowledge_changes = await sync_service.scan(config.home)
142
- display_changes(project.name, "Status", knowledge_changes, verbose)
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)