Skip to content

Commit 8bf7bdb

Browse files
groksrcphernandez
authored andcommitted
fix(cli): limit team workspace guard to bisync
Signed-off-by: Drew Cain <groksrc@gmail.com>
1 parent d0ae373 commit 8bf7bdb

2 files changed

Lines changed: 60 additions & 20 deletions

File tree

src/basic_memory/cli/commands/cloud/project_sync.py

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,9 @@
3333

3434
console = Console()
3535

36-
TEAM_WORKSPACE_SYNC_UNSUPPORTED = (
37-
"Mirror-style rclone sync/bisync is supported only for Personal workspaces.\n"
38-
"Team workspaces should not use local mirror workflows because they can "
39-
"overwrite or delete shared cloud files.\n"
40-
"Use cloud API/MCP routing for Team workspace edits, or inspect projects with "
41-
"`bm project list --workspace <workspace>`."
36+
TEAM_WORKSPACE_BISYNC_UNSUPPORTED = (
37+
"The bisync operation is only supported on Personal workspaces.\n"
38+
"Use `bm cloud sync --name {name}` instead."
4239
)
4340

4441

@@ -96,15 +93,15 @@ async def _get_workspace_for_project(name: str, config: BasicMemoryConfig) -> Wo
9693

9794

9895
def _require_personal_workspace(name: str, config: BasicMemoryConfig) -> WorkspaceInfo:
99-
"""Exit before rclone work when the target workspace is not personal."""
96+
"""Exit before bisync work when the target workspace is not personal."""
10097
try:
10198
workspace = run_with_cleanup(_get_workspace_for_project(name, config))
10299
except Exception as exc:
103100
console.print(f"[red]Error resolving workspace for project '{name}': {exc}[/red]")
104101
raise typer.Exit(1)
105102

106103
if workspace.workspace_type != "personal":
107-
console.print(f"[red]{TEAM_WORKSPACE_SYNC_UNSUPPORTED}[/red]")
104+
console.print(f"[red]{TEAM_WORKSPACE_BISYNC_UNSUPPORTED.format(name=name)}[/red]")
108105
raise typer.Exit(1)
109106

110107
return workspace
@@ -161,7 +158,6 @@ def sync_project_command(
161158
"""
162159
config = ConfigManager().config
163160
_require_cloud_credentials(config)
164-
_require_personal_workspace(name, config)
165161

166162
try:
167163
# Get tenant info for bucket name
@@ -270,7 +266,6 @@ def check_project_command(
270266
"""
271267
config = ConfigManager().config
272268
_require_cloud_credentials(config)
273-
_require_personal_workspace(name, config)
274269

275270
try:
276271
# Get tenant info for bucket name
@@ -353,7 +348,6 @@ def setup_project_sync(
353348
config_manager = ConfigManager()
354349
config = config_manager.config
355350
_require_cloud_credentials(config)
356-
_require_personal_workspace(name, config)
357351

358352
async def _verify_project_exists():
359353
"""Verify the project exists on cloud by listing all projects."""
@@ -402,8 +396,8 @@ async def _create_local_project():
402396
console.print(f"[green]Sync configured for project '{name}'[/green]")
403397
console.print(f"\nLocal sync path: {resolved_path}")
404398
console.print("\nNext steps:")
405-
console.print(f" 1. Preview: bm cloud bisync --name {name} --resync --dry-run")
406-
console.print(f" 2. Sync: bm cloud bisync --name {name} --resync")
399+
console.print(f" 1. Preview: bm cloud sync --name {name} --dry-run")
400+
console.print(f" 2. Sync: bm cloud sync --name {name}")
407401
except Exception as e:
408402
console.print(f"[red]Error configuring sync: {str(e)}[/red]")
409403
raise typer.Exit(1)

tests/cli/cloud/test_project_sync_command.py

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,15 +99,12 @@ def test_cloud_bisync_fails_fast_when_sync_entry_disappears(monkeypatch, config_
9999
@pytest.mark.parametrize(
100100
"argv",
101101
[
102-
["cloud", "sync", "--name", "research"],
103102
["cloud", "bisync", "--name", "research"],
104-
["cloud", "check", "--name", "research"],
105103
["cloud", "bisync-reset", "research"],
106-
["cloud", "sync-setup", "research", "/tmp/research"],
107104
],
108105
)
109-
def test_cloud_sync_commands_block_organization_workspace(monkeypatch, argv, config_manager):
110-
"""Rclone sync commands should fail before setup/execution for Team workspaces."""
106+
def test_cloud_bisync_commands_block_organization_workspace(monkeypatch, argv, config_manager):
107+
"""Bisync commands should fail before setup/execution for Team workspaces."""
111108
project_sync_command = importlib.import_module("basic_memory.cli.commands.cloud.project_sync")
112109

113110
config = config_manager.load_config()
@@ -130,13 +127,62 @@ def test_cloud_sync_commands_block_organization_workspace(monkeypatch, argv, con
130127
"get_mount_info",
131128
lambda: pytest.fail("workspace guard should run before mount lookup"),
132129
)
130+
monkeypatch.setattr(
131+
project_sync_command,
132+
"get_project_bisync_state",
133+
lambda _name: pytest.fail("workspace guard should run before bisync state lookup"),
134+
)
133135

134136
result = runner.invoke(app, argv)
135137

136138
assert result.exit_code == 1, result.output
137139
output = " ".join(result.output.split())
138-
assert "Mirror-style rclone sync/bisync is supported only for Personal workspaces" in output
139-
assert "overwrite or delete shared cloud files" in output
140+
assert "The bisync operation is only supported on Personal workspaces" in output
141+
assert "Use `bm cloud sync --name research` instead" in output
142+
143+
144+
def test_cloud_sync_allows_organization_workspace(monkeypatch, config_manager):
145+
"""Team workspaces can still use the one-way sync command."""
146+
project_sync_command = importlib.import_module("basic_memory.cli.commands.cloud.project_sync")
147+
148+
config = config_manager.load_config()
149+
config.cloud_api_key = "bmc_test"
150+
config.projects["research"] = ProjectEntry(
151+
path="/tmp/research",
152+
mode=ProjectMode.CLOUD,
153+
workspace_id="team-tenant",
154+
local_sync_path="/tmp/research",
155+
)
156+
config_manager.save_config(config)
157+
158+
monkeypatch.setattr(
159+
project_sync_command,
160+
"get_available_workspaces",
161+
lambda: pytest.fail("sync should not require a personal workspace"),
162+
)
163+
monkeypatch.setattr(
164+
project_sync_command,
165+
"get_mount_info",
166+
lambda: _async_value(SimpleNamespace(bucket_name="tenant-bucket")),
167+
)
168+
monkeypatch.setattr(
169+
project_sync_command,
170+
"_get_cloud_project",
171+
lambda _name: _async_value(
172+
SimpleNamespace(name="research", external_id="external-project-id", path="research")
173+
),
174+
)
175+
monkeypatch.setattr(
176+
project_sync_command,
177+
"_get_sync_project",
178+
lambda _name, _config, _project_data: (SimpleNamespace(name="research"), "/tmp/research"),
179+
)
180+
monkeypatch.setattr(project_sync_command, "project_sync", lambda *args, **kwargs: True)
181+
182+
result = runner.invoke(app, ["cloud", "sync", "--name", "research"])
183+
184+
assert result.exit_code == 0, result.output
185+
assert "research synced successfully" in result.output
140186

141187

142188
def test_require_personal_workspace_allows_personal_workspace(monkeypatch, config_manager):

0 commit comments

Comments
 (0)