chore: Remove dss-claude-plugin directory
Some checks failed
DSS Project Analysis / dss-context-update (push) Has been cancelled

Removing obsolete plugin directory after consolidation.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-10 15:38:10 -03:00
parent 6ade12b2fe
commit 72cb7319f5
50 changed files with 0 additions and 10243 deletions

View File

@@ -1,10 +0,0 @@
"""
Strategies package for DSS Claude Plugin.
This package contains the abstract base classes and concrete implementations
for different operational strategies (LOCAL vs REMOTE mode).
"""
from .base import BrowserStrategy, FilesystemStrategy
__all__ = ["BrowserStrategy", "FilesystemStrategy"]

View File

@@ -1,186 +0,0 @@
"""
Base strategy interfaces for DSS Claude Plugin.
This module defines the abstract base classes that all strategy implementations
must adhere to. These interfaces ensure consistent behavior across different
execution modes (LOCAL vs REMOTE) and allow the context to switch strategies
transparently.
"""
from abc import ABC, abstractmethod
from typing import List, Optional, Dict, Any
class BrowserStrategy(ABC):
"""
Abstract base strategy for browser interactions.
Provides methods for inspecting and interacting with a web page.
Implementations will handle the underlying automation (e.g., Playwright
for local, API calls for remote).
"""
@abstractmethod
async def get_console_logs(
self,
session_id: Optional[str] = None,
limit: int = 100,
level: Optional[str] = None
) -> List[Dict[str, Any]]:
"""
Retrieve console logs from the browser session.
Args:
session_id: The active session identifier (optional for LOCAL mode).
limit: Maximum number of logs to return.
level: Filter by log level (e.g., "log", "warn", "error").
Returns:
List of log entries containing message, level, and timestamp.
"""
pass
@abstractmethod
async def capture_screenshot(
self,
selector: Optional[str] = None,
full_page: bool = False
) -> str:
"""
Capture a screenshot of the current page or specific element.
Args:
selector: CSS selector to capture a specific element. If None,
captures the viewport.
full_page: If True, captures the full scrollable page content.
Ignored if selector is provided.
Returns:
Path to saved screenshot (LOCAL) or URL (REMOTE).
"""
pass
@abstractmethod
async def get_dom_snapshot(self) -> str:
"""
Get the current DOM state as an HTML string.
Returns:
String containing the outer HTML of the document.
"""
pass
@abstractmethod
async def get_errors(
self,
severity: Optional[str] = None,
limit: int = 50
) -> List[Dict[str, Any]]:
"""
Retrieve accumulated browser errors (console errors, crashes, network failures).
Args:
severity: Filter by error severity.
limit: Maximum number of errors to return.
Returns:
List of error details.
"""
pass
@abstractmethod
async def run_accessibility_audit(
self,
selector: Optional[str] = None
) -> Dict[str, Any]:
"""
Run accessibility audit using axe-core.
Args:
selector: CSS selector to audit specific element. If None, audits entire page.
Returns:
Dictionary with audit results containing:
- violations: List of accessibility violations
- passes: List of passing rules
- incomplete: List of rules needing review
"""
pass
@abstractmethod
async def get_performance_metrics(self) -> Dict[str, Any]:
"""
Get performance metrics including Core Web Vitals.
Returns:
Dictionary containing:
- navigation_timing: Navigation Timing API data
- core_web_vitals: TTFB, FCP, LCP, CLS metrics
"""
pass
class FilesystemStrategy(ABC):
"""
Abstract base strategy for filesystem operations.
Provides methods for reading and searching files.
Implementations ensure safe access to the filesystem in Local mode
or proxy requests in Remote mode.
"""
@abstractmethod
async def read_file(self, path: str) -> str:
"""
Read the contents of a file.
Args:
path: Relative or absolute path to the file.
Returns:
File content as string.
Raises:
FileNotFoundError: If the file does not exist.
"""
pass
@abstractmethod
async def list_directory(self, path: str) -> List[str]:
"""
List contents of a directory.
Args:
path: Directory path.
Returns:
List of filenames and directory names in the path.
"""
pass
@abstractmethod
async def search_files(self, pattern: str, path: str = ".") -> List[str]:
"""
Search for files matching a pattern.
Args:
pattern: Search pattern (glob or regex depending on implementation).
path: Root path to start search from.
Returns:
List of matching file paths.
"""
pass
@abstractmethod
async def get_file_info(self, path: str) -> Dict[str, Any]:
"""
Get metadata about a file.
Args:
path: File path.
Returns:
Dictionary containing metadata (size, created_at, modified_at).
"""
pass

View File

@@ -1,5 +0,0 @@
"""Local strategies for direct interaction with the user's environment."""
from .browser import LocalBrowserStrategy
__all__ = ["LocalBrowserStrategy"]

View File

@@ -1,455 +0,0 @@
"""
Local Browser Strategy implementation using Playwright.
Provides direct, local control over a browser for tasks like DOM inspection,
screenshotting, and running audits. This is the LOCAL mode counterpart to
RemoteBrowserStrategy which uses Shadow State pattern.
"""
import asyncio
import json
import logging
import os
import tempfile
from typing import Any, Dict, List, Optional, Type
from ..base import BrowserStrategy
# Logger setup
logger = logging.getLogger(__name__)
# URL for axe-core accessibility testing library
AXE_CORE_SCRIPT_URL = "https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.8.4/axe.min.js"
# Optional Playwright import for graceful degradation
try:
from playwright.async_api import (
Browser,
ConsoleMessage,
Error as PlaywrightError,
Page,
Playwright,
TimeoutError as PlaywrightTimeoutError,
async_playwright,
)
PLAYWRIGHT_AVAILABLE = True
except ImportError:
PLAYWRIGHT_AVAILABLE = False
# Create dummy types for type hinting when Playwright is not installed
Playwright = Type[Any]
Browser = Type[Any]
Page = Type[Any]
ConsoleMessage = Type[Any]
PlaywrightError = Exception
PlaywrightTimeoutError = Exception
class LocalBrowserStrategy(BrowserStrategy):
"""
Implements the BrowserStrategy using Playwright for local browser automation.
This strategy manages a singleton browser instance to perform actions
directly on the local machine. It is ideal for development environments
where direct access to a browser is possible.
Features:
- Browser pool pattern (reuses browser instances)
- Console log capture via CDP
- Screenshot capture (element or full page)
- DOM snapshot retrieval
- Accessibility auditing via axe-core injection
- Core Web Vitals and performance metrics
Note: This class requires Playwright to be installed.
Run `pip install "playwright"` and `playwright install chromium`.
"""
# Class-level browser pool (shared across instances)
_playwright: Optional[Playwright] = None
_browser: Optional[Browser] = None
_browser_lock = asyncio.Lock()
def __init__(self, context: Any):
"""
Initialize the LocalBrowserStrategy.
Args:
context: The DSSContext providing configuration and session info.
"""
self.context = context
self.page: Optional[Page] = None
self._console_logs: List[Any] = []
self._page_errors: List[Any] = []
if not PLAYWRIGHT_AVAILABLE:
logger.warning(
"Playwright not found. LocalBrowserStrategy will be non-functional. "
"Please run 'pip install \"playwright\"' and 'playwright install chromium'."
)
def _check_playwright(self) -> None:
"""Ensure Playwright is available, raising an error if not."""
if not PLAYWRIGHT_AVAILABLE:
raise NotImplementedError(
"Playwright is not installed. Cannot use LocalBrowserStrategy. "
"Install with: pip install playwright && playwright install chromium"
)
async def launch(self, headless: bool = True) -> None:
"""
Launch and initialize the Playwright browser instance.
This method is idempotent and ensures that a single browser instance
is shared across the application lifecycle (browser pool pattern).
Args:
headless: Whether to run browser in headless mode (default: True)
"""
self._check_playwright()
# Check if browser is already running
if LocalBrowserStrategy._browser and LocalBrowserStrategy._browser.is_connected():
logger.debug("Browser already running, reusing existing instance.")
return
async with LocalBrowserStrategy._browser_lock:
# Double-check lock to prevent race conditions
if LocalBrowserStrategy._browser and LocalBrowserStrategy._browser.is_connected():
return
logger.info("Starting Playwright...")
LocalBrowserStrategy._playwright = await async_playwright().start()
logger.info("Launching new browser instance...")
LocalBrowserStrategy._browser = await LocalBrowserStrategy._playwright.chromium.launch(
headless=headless
)
logger.info("Browser instance launched successfully.")
async def navigate(self, url: str, wait_until: str = "domcontentloaded") -> None:
"""
Navigate the browser to a specific URL.
This creates a new page context for the session, replacing any
existing page. It also sets up listeners for console logs and errors.
Args:
url: The URL to navigate to.
wait_until: The navigation event to wait for
('load', 'domcontentloaded', 'networkidle').
"""
await self.launch()
# Close existing page if any
if self.page and not self.page.is_closed():
await self.page.close()
if not LocalBrowserStrategy._browser:
raise RuntimeError("Browser is not launched. Call launch() first.")
# Create new page
self.page = await LocalBrowserStrategy._browser.new_page()
self._console_logs.clear()
self._page_errors.clear()
# Set up event listeners for log capture
self.page.on("console", self._on_console_message)
self.page.on("pageerror", self._on_page_error)
logger.info(f"Navigating to {url}...")
await self.page.goto(url, wait_until=wait_until)
logger.info(f"Navigation to {url} complete.")
def _on_console_message(self, msg: Any) -> None:
"""Handle console message events."""
self._console_logs.append(msg)
def _on_page_error(self, error: Any) -> None:
"""Handle page error events."""
self._page_errors.append(error)
async def get_console_logs(
self,
session_id: Optional[str] = None,
limit: int = 100,
level: Optional[str] = None,
) -> List[Dict[str, Any]]:
"""
Retrieve captured console logs from the current page.
Args:
session_id: Ignored in LOCAL mode (used for API compatibility).
limit: Maximum number of logs to return.
level: Filter by log level ('log', 'warn', 'error', 'info', 'debug').
Returns:
List of log entries with level, text, and location.
"""
if not self.page:
logger.warning("No active page. Returning empty logs.")
return []
logs = []
for msg in self._console_logs:
try:
log_entry = {
"level": msg.type,
"message": msg.text,
"timestamp": None, # Playwright doesn't provide timestamp directly
"category": "console",
"data": {
"location": msg.location if hasattr(msg, 'location') else None,
}
}
logs.append(log_entry)
except Exception as e:
logger.debug(f"Error processing console message: {e}")
if level:
logs = [log for log in logs if log["level"] == level]
# Return most recent logs up to limit
return logs[-limit:]
async def capture_screenshot(
self, selector: Optional[str] = None, full_page: bool = False
) -> str:
"""
Capture a screenshot of the current page or a specific element.
Args:
selector: CSS selector to capture a specific element.
If None, captures the viewport.
full_page: If True, captures the full scrollable page content.
Ignored if selector is provided.
Returns:
Path to the saved screenshot file.
Raises:
RuntimeError: If no active page is available.
"""
if not self.page or self.page.is_closed():
raise RuntimeError("No active page to capture screenshot from.")
# Generate unique filename
session_id = getattr(self.context, 'session_id', 'local')
path = os.path.join(
tempfile.gettempdir(), f"dss_screenshot_{session_id}.png"
)
try:
if selector:
element = self.page.locator(selector)
await element.screenshot(path=path, timeout=10000)
logger.info(f"Element screenshot saved to {path}")
else:
await self.page.screenshot(path=path, full_page=full_page, timeout=10000)
logger.info(f"Page screenshot saved to {path}")
return path
except Exception as e:
logger.error(f"Failed to capture screenshot: {e}")
raise
async def get_dom_snapshot(self) -> str:
"""
Get the current DOM state as an HTML string.
Returns:
String containing the outer HTML of the document.
"""
if not self.page or self.page.is_closed():
return "<!-- No active page to get DOM snapshot from. -->"
return await self.page.content()
async def get_errors(
self, severity: Optional[str] = None, limit: int = 50
) -> List[Dict[str, Any]]:
"""
Retrieve captured page errors (e.g., uncaught exceptions).
Args:
severity: Filter by severity (not yet implemented).
limit: Maximum number of errors to return.
Returns:
List of error details with name, message, and stack trace.
"""
errors = []
for err in self._page_errors:
try:
error_entry = {
"level": "error",
"category": "uncaughtError",
"message": str(err),
"data": {
"name": getattr(err, 'name', 'Error'),
"stack": getattr(err, 'stack', None),
}
}
errors.append(error_entry)
except Exception as e:
logger.debug(f"Error processing page error: {e}")
return errors[-limit:]
async def run_accessibility_audit(
self, selector: Optional[str] = None
) -> Dict[str, Any]:
"""
Run an accessibility audit on the current page using axe-core.
This injects the axe-core library into the page and runs a full
accessibility scan.
Args:
selector: A CSS selector to limit the audit to a specific element.
If None, audits the entire page.
Returns:
A dictionary containing the axe-core audit results with:
- violations: List of accessibility violations
- passes: List of passing rules
- incomplete: List of rules that need review
- inapplicable: List of rules that don't apply
Raises:
RuntimeError: If no active page is available.
"""
if not self.page or self.page.is_closed():
raise RuntimeError("No active page to run accessibility audit on.")
logger.info("Injecting axe-core library...")
await self.page.add_script_tag(url=AXE_CORE_SCRIPT_URL)
# Wait for axe to be available
await self.page.wait_for_function("typeof axe !== 'undefined'", timeout=5000)
logger.info(f"Running accessibility audit{' on ' + selector if selector else ''}...")
# Run axe with selector context if provided
if selector:
result = await self.page.evaluate(
"(selector) => axe.run(selector)", selector
)
else:
result = await self.page.evaluate("() => axe.run()")
violations_count = len(result.get('violations', []))
logger.info(f"Accessibility audit complete. Found {violations_count} violations.")
return result
async def get_performance_metrics(self) -> Dict[str, Any]:
"""
Get performance metrics, including Navigation Timing and Core Web Vitals.
Returns:
Dictionary containing:
- navigation_timing: Raw Navigation Timing API data
- core_web_vitals: FCP, LCP, and CLS metrics
Raises:
RuntimeError: If no active page is available.
"""
if not self.page or self.page.is_closed():
raise RuntimeError("No active page to get performance metrics from.")
# 1. Get Navigation Timing API metrics
timing_raw = await self.page.evaluate(
"() => JSON.stringify(window.performance.timing)"
)
nav_timing = json.loads(timing_raw)
# 2. Get Core Web Vitals via PerformanceObserver
# This script collects buffered entries and waits briefly for new ones
metrics_script = """
() => new Promise((resolve) => {
const metrics = { lcp: null, cls: 0, fcp: null, ttfb: null };
// Get TTFB from navigation timing
const navEntry = performance.getEntriesByType('navigation')[0];
if (navEntry) {
metrics.ttfb = navEntry.responseStart - navEntry.requestStart;
}
// Get FCP from paint entries
const paintEntries = performance.getEntriesByType('paint');
for (const entry of paintEntries) {
if (entry.name === 'first-contentful-paint') {
metrics.fcp = entry.startTime;
}
}
// Set up observer for LCP and CLS
try {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType === 'largest-contentful-paint') {
metrics.lcp = entry.startTime;
}
if (entry.entryType === 'layout-shift' && !entry.hadRecentInput) {
metrics.cls += entry.value;
}
}
});
observer.observe({
type: 'largest-contentful-paint',
buffered: true
});
observer.observe({
type: 'layout-shift',
buffered: true
});
// Give some time for metrics to be collected
setTimeout(() => {
observer.disconnect();
resolve(metrics);
}, 500);
} catch (e) {
// PerformanceObserver may not be fully supported
resolve(metrics);
}
})
"""
core_web_vitals = await self.page.evaluate(metrics_script)
return {
"navigation_timing": nav_timing,
"core_web_vitals": core_web_vitals
}
async def close(self) -> None:
"""
Close the current page. Browser instance is kept in pool for reuse.
To fully close the browser, use close_browser() class method.
"""
if self.page and not self.page.is_closed():
await self.page.close()
self.page = None
self._console_logs.clear()
self._page_errors.clear()
logger.info("Page closed.")
@classmethod
async def close_browser(cls) -> None:
"""
Close the browser and stop the Playwright instance.
This is a class method that closes the shared browser pool.
Should be called during application shutdown.
"""
async with cls._browser_lock:
if cls._browser:
await cls._browser.close()
cls._browser = None
logger.info("Browser instance closed.")
if cls._playwright:
await cls._playwright.stop()
cls._playwright = None
logger.info("Playwright stopped.")

View File

@@ -1,6 +0,0 @@
"""REMOTE mode strategy implementations."""
from .browser import RemoteBrowserStrategy
from .filesystem import RemoteFilesystemStrategy
__all__ = ["RemoteBrowserStrategy", "RemoteFilesystemStrategy"]

View File

@@ -1,257 +0,0 @@
"""
Remote Browser Strategy implementation.
Connects to the DSS API to retrieve browser state and logs via Shadow State pattern.
"""
import aiohttp
import asyncio
import logging
import base64
from typing import List, Dict, Any, Optional
from ..base import BrowserStrategy
from ...core.context import DSSContext
# Configure module logger
logger = logging.getLogger(__name__)
class RemoteBrowserStrategy(BrowserStrategy):
"""
Implements browser interaction via remote API calls.
Relies on the browser-side Logger to sync state to the server.
"""
def __init__(self, context: DSSContext):
"""Initialize with context."""
self.context = context
async def _get_logs_from_api(self, session_id: Optional[str] = None) -> List[Dict[str, Any]]:
"""
Fetch all logs for a specific session from the remote API.
Args:
session_id: The session ID to query. Uses default if None.
Returns:
List of log entries.
"""
if session_id is None:
session_id = self.context.session_id or "latest"
base_url = self.context.get_api_url()
# Ensure base_url doesn't have trailing slash for clean concatenation
base_url = base_url.rstrip('/')
url = f"{base_url}/api/browser-logs/{session_id}"
try:
timeout = aiohttp.ClientTimeout(total=10.0)
async with aiohttp.ClientSession(timeout=timeout) as session:
async with session.get(url) as response:
if response.status == 404:
logger.warning(f"Session {session_id} not found on remote server.")
return []
if response.status != 200:
logger.error(f"Failed to fetch logs: {response.status} {response.reason}")
return []
data = await response.json()
# The API is expected to return the exportJSON() structure from browser-logger.js
# Structure: { sessionId: "...", logs: [...], diagnostic: {...} }
return data.get("logs", [])
except aiohttp.ClientError as e:
logger.error(f"Network error fetching browser logs: {str(e)}")
return []
except Exception as e:
logger.error(f"Unexpected error in RemoteBrowserStrategy: {str(e)}")
return []
async def get_console_logs(
self,
session_id: Optional[str] = None,
limit: int = 100,
level: Optional[str] = None
) -> List[Dict[str, Any]]:
"""
Get browser console logs from the remote API.
Args:
session_id: The session ID to retrieve logs for.
limit: Maximum number of logs to return.
level: Filter by log level (log, info, warn, error).
"""
logs = await self._get_logs_from_api(session_id)
# Filter by console category mostly, but also capture uncaught errors
console_logs = [
l for l in logs
if l.get("category") in ["console", "uncaughtError", "unhandledRejection"]
]
# Filter by level if requested
if level:
console_logs = [l for l in console_logs if l.get("level") == level]
# Sort by timestamp descending (newest first)
console_logs.sort(key=lambda x: x.get("timestamp", 0), reverse=True)
return console_logs[:limit]
async def capture_screenshot(
self,
selector: Optional[str] = None,
full_page: bool = False
) -> str:
"""
Capture a screenshot.
In REMOTE mode, this requests the server to perform the capture or returns
a placeholder URL if the server capability isn't available.
Returns:
URL to screenshot or placeholder message.
"""
# Placeholder implementation until server-side rendering/CDP proxy is ready.
# Ideally, we would POST to /api/commands/{session_id}/screenshot
logger.warning("Remote screenshot capture is not yet fully implemented on server.")
# Return placeholder URL
return "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"
async def get_dom_snapshot(self) -> str:
"""
Get the current DOM snapshot via Shadow State.
Retrieves the latest log entry with category='snapshot' containing
the full HTML state captured by the browser.
"""
logs = await self._get_logs_from_api()
# Filter for snapshots
snapshots = [
l for l in logs
if l.get("category") == "snapshot" and "snapshot" in l.get("data", {})
]
if not snapshots:
return "<!-- No Shadow State snapshot available for this session -->"
# Get the latest one
latest = max(snapshots, key=lambda x: x.get("timestamp", 0))
# Extract HTML from the snapshot data object safely
# Structure in browser-logger: entry.data.snapshot.html
try:
html = latest.get("data", {}).get("snapshot", {}).get("html", "")
if html:
return html
return "<!-- Corrupted Shadow State snapshot data -->"
except (KeyError, AttributeError):
return "<!-- Corrupted or unexpected snapshot data format -->"
async def get_errors(
self,
severity: Optional[str] = None,
limit: int = 50
) -> List[Dict[str, Any]]:
"""
Get error logs from the remote API.
Args:
severity: Filter by severity (not implemented yet).
limit: Maximum number of errors to return.
Returns:
List of error entries.
"""
logs = await self._get_logs_from_api()
# Filter for errors
errors = [l for l in logs if l.get("level") == "error"]
# Sort newest first
errors.sort(key=lambda x: x.get("timestamp", 0), reverse=True)
return errors[:limit]
async def run_accessibility_audit(
self,
selector: Optional[str] = None
) -> Dict[str, Any]:
"""
Get accessibility audit results from Shadow State.
In REMOTE mode, this retrieves the accessibility data captured by the
browser-side logger using the captureAccessibilitySnapshot() method.
Args:
selector: Not used in REMOTE mode (filter not supported).
Returns:
Accessibility audit results if available in Shadow State.
"""
logs = await self._get_logs_from_api()
# Look for accessibility audits in the logs
audits = [
l for l in logs
if l.get("category") == "accessibility" or l.get("category") == "accessibilitySnapshot"
]
if not audits:
return {
"violations": [],
"passes": [],
"incomplete": [],
"message": "No accessibility audit found in Shadow State. Trigger audit from browser console using __DSS_BROWSER_LOGS.audit()"
}
# Get the latest audit
latest = max(audits, key=lambda x: x.get("timestamp", 0))
data = latest.get("data", {})
# Extract accessibility results
if "results" in data:
return data["results"]
elif "accessibility" in data:
return data["accessibility"]
else:
return data
async def get_performance_metrics(self) -> Dict[str, Any]:
"""
Get performance metrics from Shadow State.
In REMOTE mode, this retrieves Core Web Vitals and performance data
captured by the browser-side logger.
Returns:
Dictionary with performance metrics if available.
"""
logs = await self._get_logs_from_api()
# Look for performance metrics in the logs
perf_logs = [
l for l in logs
if l.get("category") in ["performance", "accessibilitySnapshot"]
]
if not perf_logs:
return {
"error": "No performance data found in Shadow State.",
"message": "Performance metrics are captured automatically during page load."
}
# Get the latest performance entry
latest = max(perf_logs, key=lambda x: x.get("timestamp", 0))
data = latest.get("data", {})
# Try to extract performance data from accessibilitySnapshot or performance entry
if "performance" in data:
return {"core_web_vitals": data["performance"]}
else:
return {"performance_data": data}

View File

@@ -1,88 +0,0 @@
"""
Remote Filesystem Strategy implementation.
Filesystem operations are restricted in REMOTE mode for security.
"""
import logging
from typing import List, Dict, Any
from pathlib import Path
from ..base import FilesystemStrategy
from ...core.context import DSSContext
# Configure module logger
logger = logging.getLogger(__name__)
class RemoteFilesystemStrategy(FilesystemStrategy):
"""
Implements filesystem operations via remote API calls.
Note: Direct filesystem access is restricted in REMOTE mode for security.
Most operations will raise NotImplementedError or return empty results.
Users should use LOCAL mode for filesystem operations.
"""
def __init__(self, context: DSSContext):
"""Initialize with context."""
self.context = context
async def read_file(self, path: str) -> str:
"""
Read file contents.
NOT AVAILABLE in REMOTE mode for security reasons.
Raises:
NotImplementedError: Always, as direct file access is restricted.
"""
logger.error("Filesystem read operations are not available in REMOTE mode.")
raise NotImplementedError(
"Direct filesystem access is restricted in REMOTE mode. "
"Please use LOCAL mode for file operations, or use API-based file uploads."
)
async def list_directory(self, path: str) -> List[str]:
"""
List directory contents.
NOT AVAILABLE in REMOTE mode for security reasons.
Raises:
NotImplementedError: Always, as directory listing is restricted.
"""
logger.error("Filesystem list operations are not available in REMOTE mode.")
raise NotImplementedError(
"Directory listing is restricted in REMOTE mode. "
"Please use LOCAL mode for filesystem operations."
)
async def search_files(self, pattern: str, path: str = ".") -> List[str]:
"""
Search for files matching a pattern.
NOT AVAILABLE in REMOTE mode for security reasons.
Raises:
NotImplementedError: Always, as file search is restricted.
"""
logger.error("Filesystem search operations are not available in REMOTE mode.")
raise NotImplementedError(
"File search is restricted in REMOTE mode. "
"Please use LOCAL mode for filesystem operations."
)
async def get_file_info(self, path: str) -> Dict[str, Any]:
"""
Get file metadata.
NOT AVAILABLE in REMOTE mode for security reasons.
Raises:
NotImplementedError: Always, as file info access is restricted.
"""
logger.error("Filesystem info operations are not available in REMOTE mode.")
raise NotImplementedError(
"File metadata access is restricted in REMOTE mode. "
"Please use LOCAL mode for filesystem operations."
)