3131project_app = typer .Typer (help = "Manage multiple Basic Memory projects" )
3232app .add_typer (project_app , name = "project" )
3333
34- config = ConfigManager ().config
35-
36-
3734def format_path (path : str ) -> str :
3835 """Format a path for display, using ~ for home directory."""
3936 home = str (Path .home ())
@@ -69,40 +66,32 @@ async def _list_projects():
6966 raise typer .Exit (1 )
7067
7168
72- if config .cloud_mode_enabled :
69+ @project_app .command ("add" )
70+ def add_project (
71+ name : str = typer .Argument (..., help = "Name of the project" ),
72+ path : str = typer .Argument (None , help = "Path to the project directory (required for local mode)" ),
73+ set_default : bool = typer .Option (False , "--default" , help = "Set as default project" ),
74+ ) -> None :
75+ """Add a new project.
7376
74- @project_app .command ("add" )
75- def add_project_cloud (
76- name : str = typer .Argument (..., help = "Name of the project" ),
77- set_default : bool = typer .Option (False , "--default" , help = "Set as default project" ),
78- ) -> None :
79- """Add a new project to Basic Memory Cloud"""
77+ For cloud mode: only name is required
78+ For local mode: both name and path are required
79+ """
80+ config = ConfigManager ().config
8081
82+ if config .cloud_mode_enabled :
83+ # Cloud mode: path not needed (auto-generated from name)
8184 async def _add_project ():
8285 async with get_client () as client :
8386 data = {"name" : name , "path" : generate_permalink (name ), "set_default" : set_default }
8487 response = await call_post (client , "/projects/projects" , json = data )
8588 return ProjectStatusResponse .model_validate (response .json ())
86-
87- try :
88- result = asyncio .run (_add_project ())
89- console .print (f"[green]{ result .message } [/green]" )
90- except Exception as e :
91- console .print (f"[red]Error adding project: { str (e )} [/red]" )
89+ else :
90+ # Local mode: path is required
91+ if path is None :
92+ console .print ("[red]Error: path argument is required in local mode[/red]" )
9293 raise typer .Exit (1 )
9394
94- # Display usage hint
95- console .print ("\n To use this project:" )
96- console .print (f" basic-memory --project={ name } <command>" )
97- else :
98-
99- @project_app .command ("add" )
100- def add_project (
101- name : str = typer .Argument (..., help = "Name of the project" ),
102- path : str = typer .Argument (..., help = "Path to the project directory" ),
103- set_default : bool = typer .Option (False , "--default" , help = "Set as default project" ),
104- ) -> None :
105- """Add a new project."""
10695 # Resolve to absolute path
10796 resolved_path = Path (os .path .abspath (os .path .expanduser (path ))).as_posix ()
10897
@@ -112,16 +101,16 @@ async def _add_project():
112101 response = await call_post (client , "/projects/projects" , json = data )
113102 return ProjectStatusResponse .model_validate (response .json ())
114103
115- try :
116- result = asyncio .run (_add_project ())
117- console .print (f"[green]{ result .message } [/green]" )
118- except Exception as e :
119- console .print (f"[red]Error adding project: { str (e )} [/red]" )
120- raise typer .Exit (1 )
104+ try :
105+ result = asyncio .run (_add_project ())
106+ console .print (f"[green]{ result .message } [/green]" )
107+ except Exception as e :
108+ console .print (f"[red]Error adding project: { str (e )} [/red]" )
109+ raise typer .Exit (1 )
121110
122- # Display usage hint
123- console .print ("\n To use this project:" )
124- console .print (f" basic-memory --project={ name } <command>" )
111+ # Display usage hint
112+ console .print ("\n To use this project:" )
113+ console .print (f" basic-memory --project={ name } <command>" )
125114
126115
127116@project_app .command ("remove" )
@@ -147,84 +136,109 @@ async def _remove_project():
147136 console .print ("[yellow]Note: The project files have not been deleted from disk.[/yellow]" )
148137
149138
150- if not config .cloud_mode_enabled :
139+ @project_app .command ("default" )
140+ def set_default_project (
141+ name : str = typer .Argument (..., help = "Name of the project to set as CLI default" ),
142+ ) -> None :
143+ """Set the default project when 'config.default_project_mode' is set.
151144
152- @project_app .command ("default" )
153- def set_default_project (
154- name : str = typer .Argument (..., help = "Name of the project to set as CLI default" ),
155- ) -> None :
156- """Set the default project when 'config.default_project_mode' is set."""
145+ Note: This command is only available in local mode.
146+ """
147+ config = ConfigManager ().config
157148
158- async def _set_default ():
159- async with get_client () as client :
160- project_permalink = generate_permalink (name )
161- response = await call_put (client , f"/projects/{ project_permalink } /default" )
162- return ProjectStatusResponse .model_validate (response .json ())
149+ if config .cloud_mode_enabled :
150+ console .print ("[red]Error: 'default' command is not available in cloud mode[/red]" )
151+ raise typer .Exit (1 )
163152
164- try :
165- result = asyncio .run (_set_default ())
166- console .print (f"[green]{ result .message } [/green]" )
167- except Exception as e :
168- console .print (f"[red]Error setting default project: { str (e )} [/red]" )
169- raise typer .Exit (1 )
153+ async def _set_default ():
154+ async with get_client () as client :
155+ project_permalink = generate_permalink (name )
156+ response = await call_put (client , f"/projects/{ project_permalink } /default" )
157+ return ProjectStatusResponse .model_validate (response .json ())
170158
171- @project_app .command ("sync-config" )
172- def synchronize_projects () -> None :
173- """Synchronize project config between configuration file and database."""
159+ try :
160+ result = asyncio .run (_set_default ())
161+ console .print (f"[green]{ result .message } [/green]" )
162+ except Exception as e :
163+ console .print (f"[red]Error setting default project: { str (e )} [/red]" )
164+ raise typer .Exit (1 )
174165
175- async def _sync_config ():
176- async with get_client () as client :
177- response = await call_post (client , "/projects/config/sync" )
178- return ProjectStatusResponse .model_validate (response .json ())
179166
180- try :
181- result = asyncio .run (_sync_config ())
182- console .print (f"[green]{ result .message } [/green]" )
183- except Exception as e : # pragma: no cover
184- console .print (f"[red]Error synchronizing projects: { str (e )} [/red]" )
185- raise typer .Exit (1 )
167+ @project_app .command ("sync-config" )
168+ def synchronize_projects () -> None :
169+ """Synchronize project config between configuration file and database.
186170
187- @project_app .command ("move" )
188- def move_project (
189- name : str = typer .Argument (..., help = "Name of the project to move" ),
190- new_path : str = typer .Argument (..., help = "New absolute path for the project" ),
191- ) -> None :
192- """Move a project to a new location."""
193- # Resolve to absolute path
194- resolved_path = Path (os .path .abspath (os .path .expanduser (new_path ))).as_posix ()
171+ Note: This command is only available in local mode.
172+ """
173+ config = ConfigManager ().config
195174
196- async def _move_project ():
197- async with get_client () as client :
198- data = {"path" : resolved_path }
199- project_permalink = generate_permalink (name )
175+ if config .cloud_mode_enabled :
176+ console .print ("[red]Error: 'sync-config' command is not available in cloud mode[/red]" )
177+ raise typer .Exit (1 )
200178
201- # TODO fix route to use ProjectPathDep
202- response = await call_patch (
203- client , f"/{ name } /project/{ project_permalink } " , json = data
204- )
205- return ProjectStatusResponse .model_validate (response .json ())
179+ async def _sync_config ():
180+ async with get_client () as client :
181+ response = await call_post (client , "/projects/config/sync" )
182+ return ProjectStatusResponse .model_validate (response .json ())
206183
207- try :
208- result = asyncio .run (_move_project ())
209- console .print (f"[green]{ result .message } [/green]" )
184+ try :
185+ result = asyncio .run (_sync_config ())
186+ console .print (f"[green]{ result .message } [/green]" )
187+ except Exception as e : # pragma: no cover
188+ console .print (f"[red]Error synchronizing projects: { str (e )} [/red]" )
189+ raise typer .Exit (1 )
210190
211- # Show important file movement reminder
212- console .print () # Empty line for spacing
213- console .print (
214- Panel (
215- "[bold red]IMPORTANT:[/bold red] Project configuration updated successfully.\n \n "
216- "[yellow]You must manually move your project files from the old location to:[/yellow]\n "
217- f"[cyan]{ resolved_path } [/cyan]\n \n "
218- "[dim]Basic Memory has only updated the configuration - your files remain in their original location.[/dim]" ,
219- title = "⚠️ Manual File Movement Required" ,
220- border_style = "yellow" ,
221- expand = False ,
222- )
191+
192+ @project_app .command ("move" )
193+ def move_project (
194+ name : str = typer .Argument (..., help = "Name of the project to move" ),
195+ new_path : str = typer .Argument (..., help = "New absolute path for the project" ),
196+ ) -> None :
197+ """Move a project to a new location.
198+
199+ Note: This command is only available in local mode.
200+ """
201+ config = ConfigManager ().config
202+
203+ if config .cloud_mode_enabled :
204+ console .print ("[red]Error: 'move' command is not available in cloud mode[/red]" )
205+ raise typer .Exit (1 )
206+
207+ # Resolve to absolute path
208+ resolved_path = Path (os .path .abspath (os .path .expanduser (new_path ))).as_posix ()
209+
210+ async def _move_project ():
211+ async with get_client () as client :
212+ data = {"path" : resolved_path }
213+ project_permalink = generate_permalink (name )
214+
215+ # TODO fix route to use ProjectPathDep
216+ response = await call_patch (
217+ client , f"/{ name } /project/{ project_permalink } " , json = data
223218 )
219+ return ProjectStatusResponse .model_validate (response .json ())
224220
225- except Exception as e :
226- console .print (f"[red]Error moving project: { str (e )} [/red]" )
227- raise typer .Exit (1 )
221+ try :
222+ result = asyncio .run (_move_project ())
223+ console .print (f"[green]{ result .message } [/green]" )
224+
225+ # Show important file movement reminder
226+ console .print () # Empty line for spacing
227+ console .print (
228+ Panel (
229+ "[bold red]IMPORTANT:[/bold red] Project configuration updated successfully.\n \n "
230+ "[yellow]You must manually move your project files from the old location to:[/yellow]\n "
231+ f"[cyan]{ resolved_path } [/cyan]\n \n "
232+ "[dim]Basic Memory has only updated the configuration - your files remain in their original location.[/dim]" ,
233+ title = "⚠️ Manual File Movement Required" ,
234+ border_style = "yellow" ,
235+ expand = False ,
236+ )
237+ )
238+
239+ except Exception as e :
240+ console .print (f"[red]Error moving project: { str (e )} [/red]" )
241+ raise typer .Exit (1 )
228242
229243
230244@project_app .command ("info" )
0 commit comments