Skip to content

Commit 7b6d62b

Browse files
phernandezclaude
andcommitted
feat: implement Phase 2.3 project auto-registration (SPEC-9)
Adds automatic project registration when syncing with cloud. Changes: - Add fetch_cloud_projects() to GET /proxy/projects/projects - Add scan_local_directories() to find local project folders - Add create_cloud_project() to POST /proxy/projects/projects - Integrate auto-registration into run_bisync() before sync Behavior: - On each bisync run (except dry-run/resync), checks for new local projects - Compares local directories with cloud project list - Auto-creates missing projects on cloud via API - Waits for 201 response before continuing with sync - Gracefully handles errors and continues sync Benefits: - ✅ Local directories auto-create cloud projects - ✅ No manual coordination needed - ✅ Seamless multi-project workflow - ✅ Users just create folders and sync Related: SPEC-9 Multi-Project Bidirectional Sync Architecture 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> Signed-off-by: phernandez <paul@basicmachines.co>
1 parent 176bae5 commit 7b6d62b

File tree

2 files changed

+116
-6
lines changed

2 files changed

+116
-6
lines changed

specs/SPEC-9 Multi-Project Bidirectional Sync Architecture.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -178,12 +178,12 @@ This spec affects:
178178
- [x] Update `setup_cloud_bisync()` to use bucket root
179179
- [ ] Test with multiple projects
180180

181-
**2.3 Project Auto-Registration (Bisync)** (Deferred to basic-memory-cloud)
182-
- [ ] Add `fetch_cloud_projects()` function (GET /proxy/projects/projects)
183-
- [ ] Add `scan_local_directories()` function
184-
- [ ] Add `create_cloud_project()` function (POST /proxy/projects/projects)
185-
- [ ] Integrate into `run_bisync()`: fetch → scan → create missing → sync
186-
- [ ] Wait for API 201 response before syncing
181+
**2.3 Project Auto-Registration (Bisync)**
182+
- [x] Add `fetch_cloud_projects()` function (GET /proxy/projects/projects)
183+
- [x] Add `scan_local_directories()` function
184+
- [x] Add `create_cloud_project()` function (POST /proxy/projects/projects)
185+
- [x] Integrate into `run_bisync()`: fetch → scan → create missing → sync
186+
- [x] Wait for API 201 response before syncing
187187

188188
**2.4 Bisync Directory Configuration**
189189
- [x] Add `--dir` parameter to `bm cloud bisync-setup`

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

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,81 @@ async def generate_mount_credentials(tenant_id: str) -> dict:
103103
raise BisyncError(f"Failed to generate credentials: {e}") from e
104104

105105

106+
async def fetch_cloud_projects() -> dict:
107+
"""Fetch list of projects from cloud API.
108+
109+
Returns:
110+
Dict with 'projects' list from cloud
111+
"""
112+
try:
113+
config_manager = ConfigManager()
114+
config = config_manager.config
115+
host_url = config.cloud_host.rstrip("/")
116+
117+
response = await make_api_request(method="GET", url=f"{host_url}/proxy/projects/projects")
118+
119+
return response.json()
120+
except Exception as e:
121+
raise BisyncError(f"Failed to fetch cloud projects: {e}") from e
122+
123+
124+
def scan_local_directories(sync_dir: Path) -> list[str]:
125+
"""Scan local sync directory for project folders.
126+
127+
Args:
128+
sync_dir: Path to bisync directory
129+
130+
Returns:
131+
List of directory names (project names)
132+
"""
133+
if not sync_dir.exists():
134+
return []
135+
136+
directories = []
137+
for item in sync_dir.iterdir():
138+
if item.is_dir() and not item.name.startswith("."):
139+
directories.append(item.name)
140+
141+
return directories
142+
143+
144+
async def create_cloud_project(project_name: str) -> dict:
145+
"""Create a new project on cloud.
146+
147+
Args:
148+
project_name: Name of project to create
149+
150+
Returns:
151+
Project details from API response
152+
"""
153+
try:
154+
config_manager = ConfigManager()
155+
config = config_manager.config
156+
host_url = config.cloud_host.rstrip("/")
157+
158+
# Use generate_permalink to ensure consistent naming
159+
from basic_memory.utils import generate_permalink
160+
161+
project_path = generate_permalink(project_name)
162+
163+
project_data = {
164+
"name": project_name,
165+
"path": project_path,
166+
"set_default": False,
167+
}
168+
169+
response = await make_api_request(
170+
method="POST",
171+
url=f"{host_url}/proxy/projects/projects",
172+
headers={"Content-Type": "application/json"},
173+
json_data=project_data,
174+
)
175+
176+
return response.json()
177+
except Exception as e:
178+
raise BisyncError(f"Failed to create cloud project '{project_name}': {e}") from e
179+
180+
106181
def get_bisync_state_path(tenant_id: str) -> Path:
107182
"""Get path to bisync state directory."""
108183
return Path.home() / ".basic-memory" / "bisync-state" / tenant_id
@@ -403,6 +478,41 @@ def run_bisync(
403478

404479
profile = BISYNC_PROFILES[profile_name]
405480

481+
# Auto-register projects before sync (unless dry-run or resync)
482+
if not dry_run and not resync:
483+
try:
484+
console.print("[dim]Checking for new projects...[/dim]")
485+
486+
# Fetch cloud projects
487+
cloud_data = asyncio.run(fetch_cloud_projects())
488+
cloud_project_names = {p["name"] for p in cloud_data.get("projects", [])}
489+
490+
# Scan local directories
491+
local_dirs = scan_local_directories(local_path)
492+
493+
# Create missing cloud projects
494+
new_projects = []
495+
for dir_name in local_dirs:
496+
if dir_name not in cloud_project_names:
497+
new_projects.append(dir_name)
498+
499+
if new_projects:
500+
console.print(
501+
f"[blue]Found {len(new_projects)} new local project(s), creating on cloud...[/blue]"
502+
)
503+
for project_name in new_projects:
504+
try:
505+
asyncio.run(create_cloud_project(project_name))
506+
console.print(f"[green] ✓ Created project: {project_name}[/green]")
507+
except BisyncError as e:
508+
console.print(f"[yellow] ⚠ Could not create {project_name}: {e}[/yellow]")
509+
else:
510+
console.print("[dim]All local projects already registered on cloud[/dim]")
511+
512+
except Exception as e:
513+
console.print(f"[yellow]Warning: Project auto-registration failed: {e}[/yellow]")
514+
console.print("[yellow]Continuing with sync anyway...[/yellow]")
515+
406516
# Check if first run and require resync
407517
if not resync and not bisync_state_exists(tenant_id) and not dry_run:
408518
raise BisyncError(

0 commit comments

Comments
 (0)