feat(config): Add cloud_projects schema for project-scoped sync (SPEC-20 Phase 1)

Add CloudProjectConfig model and cloud_projects dict to BasicMemoryConfig:
- CloudProjectConfig tracks local_path, last_sync, bisync_initialized
- cloud_projects: dict[str, CloudProjectConfig] in config
- Fix datetime serialization with mode='json' in model_dump()
- Add comprehensive tests for cloud_projects functionality
- Backward compatible: old configs without cloud_projects load correctly

This enables project-scoped sync configuration tracked in config file
rather than filesystem discovery, preventing phantom projects.

Part of SPEC-20 Simplified Project-Scoped Rclone Sync.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: phernandez <paul@basicmachines.co>
This commit is contained in:
phernandez
2025-10-28 10:35:04 -05:00
parent a92741985a
commit 8bc550f613
3 changed files with 128 additions and 4 deletions
+30 -2
View File
@@ -3,11 +3,12 @@
import json
import os
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, Literal, Optional, List, Tuple
from loguru import logger
from pydantic import Field, field_validator
from pydantic import BaseModel, Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
import basic_memory
@@ -39,6 +40,26 @@ class ProjectConfig:
return f"/{generate_permalink(self.name)}"
class CloudProjectConfig(BaseModel):
"""Sync configuration for a cloud project.
This tracks the local working directory and sync state for a project
that is synced with Basic Memory Cloud.
"""
local_path: str = Field(
description="Local working directory path for this cloud project"
)
last_sync: Optional[datetime] = Field(
default=None,
description="Timestamp of last successful sync operation"
)
bisync_initialized: bool = Field(
default=False,
description="Whether rclone bisync baseline has been established"
)
class BasicMemoryConfig(BaseSettings):
"""Pydantic model for Basic Memory global configuration."""
@@ -138,6 +159,11 @@ class BasicMemoryConfig(BaseSettings):
description="Enable cloud mode - all requests go to cloud instead of local (config file value)",
)
cloud_projects: Dict[str, CloudProjectConfig] = Field(
default_factory=dict,
description="Cloud project sync configuration mapping project names to their local paths and sync state",
)
@property
def cloud_mode_enabled(self) -> bool:
"""Check if cloud mode is enabled.
@@ -427,7 +453,9 @@ def get_project_config(project_name: Optional[str] = None) -> ProjectConfig:
def save_basic_memory_config(file_path: Path, config: BasicMemoryConfig) -> None:
"""Save configuration to file."""
try:
file_path.write_text(json.dumps(config.model_dump(), indent=2))
# Use model_dump with mode='json' to serialize datetime objects properly
config_dict = config.model_dump(mode='json')
file_path.write_text(json.dumps(config_dict, indent=2))
except Exception as e: # pragma: no cover
logger.error(f"Failed to save config: {e}")