Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
182 lines
6.3 KiB
Python
182 lines
6.3 KiB
Python
"""
|
|
DSS Context Module
|
|
==================
|
|
|
|
Singleton context manager for the DSS Plugin.
|
|
Handles configuration loading, mode detection, and strategy instantiation.
|
|
"""
|
|
|
|
import asyncio
|
|
import logging
|
|
from typing import Optional, Dict, Any
|
|
|
|
from .config import DSSConfig, DSSMode
|
|
|
|
# Logger setup
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Protocol/Type placeholder for Strategies (to be replaced by base class in next steps)
|
|
Strategy = Any
|
|
|
|
|
|
class DSSContext:
|
|
"""
|
|
Singleton context manager for the DSS Plugin.
|
|
|
|
Handles configuration loading, mode detection (Local/Remote),
|
|
and strategy instantiation.
|
|
"""
|
|
_instance: Optional['DSSContext'] = None
|
|
_lock: asyncio.Lock = asyncio.Lock()
|
|
|
|
def __init__(self) -> None:
|
|
"""
|
|
Private initializer. Use get_instance() instead.
|
|
"""
|
|
if DSSContext._instance is not None:
|
|
raise RuntimeError("DSSContext is a singleton. Use get_instance() to access it.")
|
|
|
|
self.config: Optional[DSSConfig] = None
|
|
self.active_mode: DSSMode = DSSMode.REMOTE # Default safe fallback
|
|
self._capabilities: Dict[str, bool] = {}
|
|
self._strategy_cache: Dict[str, Strategy] = {}
|
|
self.session_id: Optional[str] = None
|
|
|
|
@classmethod
|
|
async def get_instance(cls) -> 'DSSContext':
|
|
"""
|
|
Async factory method to get the singleton instance.
|
|
Ensures config is loaded and mode is detected before returning.
|
|
"""
|
|
if not cls._instance:
|
|
async with cls._lock:
|
|
# Double-check locking pattern
|
|
if not cls._instance:
|
|
instance = cls()
|
|
await instance._initialize()
|
|
cls._instance = instance
|
|
|
|
return cls._instance
|
|
|
|
@classmethod
|
|
def reset(cls) -> None:
|
|
"""
|
|
Resets the singleton instance. Useful for testing.
|
|
"""
|
|
cls._instance = None
|
|
|
|
async def _initialize(self) -> None:
|
|
"""
|
|
Internal initialization logic:
|
|
1. Load Config
|
|
2. Detect Mode
|
|
3. Cache Capabilities
|
|
"""
|
|
try:
|
|
# 1. Load Configuration
|
|
self.config = DSSConfig.load()
|
|
self.session_id = self.config.session_id
|
|
|
|
# 2. Detect Mode (Async check)
|
|
self.active_mode = await self.config.get_active_mode()
|
|
|
|
logger.info(f"DSSContext initialized. Mode: {self.active_mode.value}, Session: {self.session_id}")
|
|
|
|
# 3. Cache Capabilities
|
|
self._cache_capabilities()
|
|
|
|
except Exception as e:
|
|
logger.error(f"Failed to initialize DSSContext: {e}")
|
|
# Fallback to defaults if initialization fails
|
|
self.active_mode = DSSMode.REMOTE
|
|
self._capabilities = {"limited": True}
|
|
|
|
def _cache_capabilities(self) -> None:
|
|
"""
|
|
Determines what the plugin can do based on the active mode.
|
|
"""
|
|
# Base capabilities
|
|
caps = {
|
|
"can_read_files": False,
|
|
"can_execute_browser": False,
|
|
"can_screenshot": False,
|
|
"can_connect_remote": True
|
|
}
|
|
|
|
if self.active_mode == DSSMode.LOCAL:
|
|
# Local mode allows direct filesystem access and local browser control
|
|
caps["can_read_files"] = True
|
|
caps["can_execute_browser"] = True
|
|
caps["can_screenshot"] = True
|
|
elif self.active_mode == DSSMode.REMOTE:
|
|
# Remote mode relies on API capabilities
|
|
# Depending on remote configuration, these might differ
|
|
caps["can_execute_browser"] = False # Typically restricted in pure remote unless via API
|
|
caps["can_read_files"] = False # Security restriction
|
|
|
|
self._capabilities = caps
|
|
|
|
def get_capability(self, key: str) -> bool:
|
|
"""Check if a specific capability is active."""
|
|
return self._capabilities.get(key, False)
|
|
|
|
def get_api_url(self) -> str:
|
|
"""Get the correct API URL for the current mode."""
|
|
if self.config is None:
|
|
return "https://dss.overbits.luz.uy" # Default fallback
|
|
return self.config.get_api_url(self.active_mode)
|
|
|
|
def get_strategy(self, strategy_type: str) -> Any:
|
|
"""
|
|
Factory method to retrieve operational strategies.
|
|
|
|
Args:
|
|
strategy_type: One of 'browser', 'filesystem', 'screenshot'
|
|
|
|
Returns:
|
|
An instance of the requested strategy.
|
|
"""
|
|
# Return cached strategy if available
|
|
if strategy_type in self._strategy_cache:
|
|
return self._strategy_cache[strategy_type]
|
|
|
|
strategy_instance = None
|
|
|
|
# NOTE: Strategy classes will be implemented in the next step.
|
|
# We use local imports here to avoid circular dependency issues
|
|
# if strategies define their own types using DSSContext.
|
|
|
|
try:
|
|
if strategy_type == "browser":
|
|
# Will be implemented in Phase 2 & 3
|
|
if self.active_mode == DSSMode.LOCAL:
|
|
from ..strategies.local.browser import LocalBrowserStrategy
|
|
strategy_instance = LocalBrowserStrategy(self)
|
|
else:
|
|
from ..strategies.remote.browser import RemoteBrowserStrategy
|
|
strategy_instance = RemoteBrowserStrategy(self)
|
|
|
|
elif strategy_type == "filesystem":
|
|
# Will be implemented in Phase 2
|
|
if self.active_mode == DSSMode.LOCAL:
|
|
from ..strategies.local.filesystem import LocalFilesystemStrategy
|
|
strategy_instance = LocalFilesystemStrategy(self)
|
|
else:
|
|
from ..strategies.remote.filesystem import RemoteFilesystemStrategy
|
|
strategy_instance = RemoteFilesystemStrategy(self)
|
|
|
|
elif strategy_type == "screenshot":
|
|
# Screenshot is part of browser strategy
|
|
return self.get_strategy("browser")
|
|
|
|
else:
|
|
raise ValueError(f"Unknown strategy type: {strategy_type}")
|
|
|
|
except ImportError as e:
|
|
logger.error(f"Failed to import strategy {strategy_type}: {e}")
|
|
raise NotImplementedError(f"Strategy {strategy_type} not yet implemented") from e
|
|
|
|
# Cache and return
|
|
self._strategy_cache[strategy_type] = strategy_instance
|
|
return strategy_instance
|