2626 generate_mount_credentials ,
2727 get_mount_info ,
2828)
29- from basic_memory .cli .commands .cloud .rclone_config import configure_rclone_remote
29+ from basic_memory .cli .commands .cloud .rclone_config import (
30+ configure_rclone_remote ,
31+ remote_name_for_workspace ,
32+ )
3033from basic_memory .cli .commands .cloud .rclone_installer import (
3134 RcloneInstallError ,
3235 install_rclone ,
3336)
37+ from basic_memory .mcp .project_context import get_available_workspaces
38+ from basic_memory .schemas .cloud import (
39+ WorkspaceInfo ,
40+ format_workspace_choices ,
41+ format_workspace_selection_choices ,
42+ workspace_matches_exact_identifier ,
43+ )
3444
3545console = Console ()
3646
3747
48+ def _resolve_setup_workspace (identifier : str ) -> WorkspaceInfo :
49+ """Resolve a workspace identifier (slug, name, or tenant_id) for setup.
50+
51+ Errors with copyable choices when the identifier matches zero or multiple
52+ workspaces, so the user can disambiguate.
53+ """
54+ workspaces = run_with_cleanup (get_available_workspaces ())
55+ if not workspaces :
56+ console .print ("[red]No accessible cloud workspaces found for this account[/red]" )
57+ raise typer .Exit (1 )
58+
59+ matches = [ws for ws in workspaces if workspace_matches_exact_identifier (ws , identifier )]
60+ if len (matches ) == 1 :
61+ return matches [0 ]
62+
63+ if not matches :
64+ console .print (f"[red]No workspace matches '{ identifier } '[/red]" )
65+ console .print ("\n Available workspaces:" )
66+ console .print (format_workspace_choices (workspaces ))
67+ else :
68+ console .print (f"[red]'{ identifier } ' matches multiple workspaces[/red]" )
69+ console .print ("\n Disambiguate with the workspace slug or tenant_id:" )
70+ console .print (format_workspace_selection_choices (matches ))
71+ raise typer .Exit (1 )
72+
73+
3874@cloud_app .command ()
3975def login ():
4076 """Authenticate with WorkOS using OAuth Device Authorization flow."""
@@ -164,13 +200,23 @@ def status() -> None:
164200
165201
166202@cloud_app .command ("setup" )
167- def setup () -> None :
203+ def setup (
204+ workspace : str | None = typer .Option (
205+ None ,
206+ "--workspace" ,
207+ help = "Set up sync for a specific workspace (slug, name, or tenant_id). "
208+ "Omit for your default workspace." ,
209+ ),
210+ ) -> None :
168211 """Set up cloud sync by installing rclone and configuring credentials.
169212
170- After setup, use project commands for syncing:
171- bm project add <name> --cloud --local-path ~/projects/<name>
172- bm project bisync --name <name> --resync # First time
173- bm project bisync --name <name> # Subsequent syncs
213+ Run once per workspace you sync. The default workspace uses the
214+ 'basic-memory-cloud' remote; other (e.g. Team) workspaces each get their own
215+ tenant-scoped remote, since Tigris credentials are bucket-scoped.
216+
217+ After setup, use the cloud sync commands:
218+ bm cloud pull --name <name> # fetch cloud changes (Team-safe)
219+ bm cloud push --name <name> # upload local changes (Team-safe)
174220 """
175221 console .print ("[bold blue]Basic Memory Cloud Setup[/bold blue]" )
176222 console .print ("Setting up cloud sync with rclone...\n " )
@@ -180,42 +226,55 @@ def setup() -> None:
180226 console .print ("[blue]Step 1: Installing rclone...[/blue]" )
181227 install_rclone ()
182228
183- # Step 2: Get tenant info
229+ # --- Resolve target workspace ---
230+ # Trigger: --workspace given. Why: Tigris keys are tenant-scoped, so a
231+ # non-default workspace needs its own bucket + remote. Outcome: scope the
232+ # mount-info/credentials calls and name the remote after the workspace.
233+ if workspace is not None :
234+ target = _resolve_setup_workspace (workspace )
235+ workspace_id : str | None = target .tenant_id
236+ remote_name = remote_name_for_workspace (target .slug , is_default = target .is_default )
237+ console .print (f"[dim]Workspace: { target .name } ({ target .slug } )[/dim]" )
238+ else :
239+ workspace_id = None # default tenant
240+ remote_name = remote_name_for_workspace (None , is_default = True )
241+
242+ # Step 2: Get tenant info (scoped to the target workspace when given)
184243 console .print ("\n [blue]Step 2: Getting tenant information...[/blue]" )
185- tenant_info = run_with_cleanup (get_mount_info ())
244+ tenant_info = run_with_cleanup (get_mount_info (workspace_id = workspace_id ))
186245 console .print (f"[green]Found tenant: { tenant_info .tenant_id } [/green]" )
187246
188- # Step 3: Generate credentials
247+ # Step 3: Generate credentials for that tenant's bucket
189248 console .print ("\n [blue]Step 3: Generating sync credentials...[/blue]" )
190249 creds = run_with_cleanup (generate_mount_credentials (tenant_info .tenant_id ))
191250 console .print ("[green]Generated secure credentials[/green]" )
192251
193- # Step 4: Configure rclone remote
252+ # Step 4: Configure the tenant's rclone remote
194253 console .print ("\n [blue]Step 4: Configuring rclone remote...[/blue]" )
195254 configure_rclone_remote (
196255 access_key = creds .access_key ,
197256 secret_key = creds .secret_key ,
257+ remote_name = remote_name ,
198258 )
199259
200260 console .print ("\n [bold green]Cloud setup completed successfully![/bold green]" )
201261 console .print ("\n [bold]Next steps:[/bold]" )
202- console .print ("1. Add a project with local sync path:" )
203- console .print (" bm project add research --cloud --local-path ~/Documents/research" )
204- console .print ("\n Or configure sync for an existing project:" )
262+ console .print ("1. Configure sync for a project:" )
205263 console .print (" bm cloud sync-setup research ~/Documents/research" )
206- console .print ("\n 2. Preview the initial sync (recommended):" )
207- console .print (" bm project bisync --name research --resync --dry-run" )
208- console .print ("\n 3. If all looks good, run the actual sync:" )
209- console .print (" bm project bisync --name research --resync" )
210- console .print ("\n 4. Subsequent syncs (no --resync needed):" )
211- console .print (" bm project bisync --name research" )
264+ console .print ("\n 2. Preview a pull (recommended):" )
265+ console .print (" bm cloud pull --name research --dry-run" )
266+ console .print ("\n 3. Fetch cloud changes / upload local changes:" )
267+ console .print (" bm cloud pull --name research" )
268+ console .print (" bm cloud push --name research" )
212269 console .print (
213270 "\n [dim]Tip: Always use --dry-run first to preview changes before syncing[/dim]"
214271 )
215272
216273 except (RcloneInstallError , BisyncError , CloudAPIError ) as e :
217274 console .print (f"\n [red]Setup failed: { e } [/red]" )
218275 raise typer .Exit (1 )
276+ except typer .Exit :
277+ raise
219278 except Exception as e :
220279 console .print (f"\n [red]Unexpected error during setup: { e } [/red]" )
221280 raise typer .Exit (1 )
0 commit comments