diff --git a/osa/application/di.py b/osa/application/di.py index 4fb5eec..cdd63b3 100644 --- a/osa/application/di.py +++ b/osa/application/di.py @@ -1,5 +1,6 @@ from dishka import AsyncContainer, make_async_container +from osa.cli.util.paths import OSAPaths from osa.config import Config from osa.domain.deposition.util.di import DepositionProvider from osa.domain.validation.util.di import ValidationProvider @@ -14,6 +15,9 @@ def create_container() -> AsyncContainer: config = Config() + # OSAPaths reads OSA_DATA_DIR from environment automatically + paths = OSAPaths() + return make_async_container( PersistenceProvider(), OciProvider(), @@ -22,6 +26,6 @@ def create_container() -> AsyncContainer: EventProvider(), DepositionProvider(), ValidationProvider(), - context={Config: config}, + context={Config: config, OSAPaths: paths}, scopes=Scope, # type: ignore[arg-type] # Custom scope class ) diff --git a/osa/cli/commands/admin.py b/osa/cli/commands/admin.py index e9c5d60..332f48d 100644 --- a/osa/cli/commands/admin.py +++ b/osa/cli/commands/admin.py @@ -1,103 +1,15 @@ """Administrative commands for OSA management.""" -import shutil -import sys from pathlib import Path import cyclopts from osa.cli.console import get_console -from osa.cli.util import DaemonManager, OSAPaths, ServerStatus +from osa.cli.util import OSAPaths, read_server_state app = cyclopts.App(name="admin", help="Administrative commands") -@app.command -def clean( - force: bool = False, - keep_config: bool = False, - keep_logs: bool = False, -) -> None: - """Wipe OSA data directories to start fresh. - - Stops the server if running, then removes: - - ~/.local/share/osa/ (database, vectors) - - ~/.local/state/osa/ (server state, logs) - - ~/.cache/osa/ (search cache) - - ~/.config/osa/ (unless --keep-config) - - Args: - force: Skip confirmation prompt. - keep_config: Keep the config directory (~/.config/osa). - keep_logs: Keep the logs directory. - """ - console = get_console() - paths = OSAPaths() - - # Check which directories exist - dirs_to_clean: list[tuple[str, Path]] = [] - if paths.data_dir.exists(): - dirs_to_clean.append(("Data", paths.data_dir)) - if paths.state_dir.exists(): - dirs_to_clean.append(("State", paths.state_dir)) - if paths.cache_dir.exists(): - dirs_to_clean.append(("Cache", paths.cache_dir)) - if paths.config_dir.exists() and not keep_config: - dirs_to_clean.append(("Config", paths.config_dir)) - - if not dirs_to_clean: - console.info("Nothing to clean - no OSA directories exist") - return - - # Check if server is running - daemon = DaemonManager() - status_info = daemon.status() - - if status_info.status == ServerStatus.RUNNING: - if not force: - console.warning(f"Server is running (PID {status_info.pid})") - response = input("Stop server and clean? [y/N] ").strip().lower() - if response != "y": - console.info("Aborted") - sys.exit(1) - console.info("Stopping server...") - daemon.stop() - - # Confirm before wiping - if not force: - console.print("[bold]This will delete:[/bold]") - for name, path in dirs_to_clean: - console.print(f" [cyan]{path}[/cyan] [dim]({name.lower()})[/dim]") - if keep_logs: - console.print(f" [dim](keeping logs at {paths.logs_dir})[/dim]") - if keep_config and paths.config_dir.exists(): - console.print(f" [dim](keeping config at {paths.config_dir})[/dim]") - console.print() - response = input("Are you sure? [y/N] ").strip().lower() - if response != "y": - console.info("Aborted") - sys.exit(1) - - # Perform cleanup - for name, path in dirs_to_clean: - if name == "State" and keep_logs: - # Delete everything in state except logs - for item in path.iterdir(): - if item == paths.logs_dir: - continue - if item.is_dir(): - shutil.rmtree(item) - else: - item.unlink() - console.success(f"Cleaned {path} (logs preserved)") - else: - shutil.rmtree(path) - console.success(f"Removed {path}") - - console.print() - console.info("Run 'osa init' to set up OSA again") - - @app.command def info() -> None: """Show information about OSA directories and status.""" @@ -147,7 +59,7 @@ def exists_marker(path: Path) -> str: # Server status console.print() - state = paths.read_server_state() + state = read_server_state(paths.server_state_file) if state: from osa.cli.console import relative_time diff --git a/osa/cli/commands/init.py b/osa/cli/commands/init.py deleted file mode 100644 index f814da5..0000000 --- a/osa/cli/commands/init.py +++ /dev/null @@ -1,143 +0,0 @@ -"""Initialize OSA configuration and directories.""" - -import sys -from typing import Literal - -import cyclopts - -from osa.cli.console import get_console -from osa.cli.util import OSAPaths - -app = cyclopts.App(name="init", help="Initialize OSA with a template") - -# Available templates -Template = Literal["geo", "minimal"] -TEMPLATES: list[Template] = ["geo", "minimal"] - -GEO_TEMPLATE = """\ -# OSA Configuration - GEO Template - -server: - name: "My OSA Node" - domain: "localhost" - -# Database (SQLite by default, stored in ~/.local/share/osa/) -# database: -# auto_migrate: true - -# Logging -# logging: -# level: "INFO" - -# GEO Ingestor - pulls from NCBI Gene Expression Omnibus via Entrez API -ingestors: - - ingestor: geo-entrez - config: - record_type: gse # gse (~250k all) or gds (~5k curated) - email: your@email.com # Required by NCBI - please update this - # api_key: null # Optional: NCBI API key for higher rate limits (https://account.ncbi.nlm.nih.gov/settings/) - initial_run: - enabled: true - limit: 50 - # schedule: - # cron: "0 * * * *" # Hourly - # limit: 100 - -# Vector search index -indexes: - - name: vector - backend: vector - config: - persist_dir: {vectors_dir} - embedding: - model: all-MiniLM-L6-v2 - fields: [title, summary, organism, platform, entry_type] -""" - -MINIMAL_TEMPLATE = """\ -# OSA Configuration - -server: - name: "My OSA Node" - domain: "localhost" - -# Database (SQLite by default, stored in ~/.local/share/osa/) -# database: -# auto_migrate: true - -# Logging -# logging: -# level: "INFO" - -# Add your ingestors here: -# ingestors: -# - ingestor: geo-entrez -# config: -# email: your@email.com - -# Add your indexes here: -# indexes: -# - name: my-index -# backend: vector -# config: -# persist_dir: {vectors_dir} -""" - - -@app.default -def init( - template: Template | None = None, - /, - force: bool = False, -) -> None: - """Initialize OSA configuration and directories. - - Creates the config file and directory structure needed to run OSA. - - Args: - template: Template to use (geo, minimal). - force: Overwrite existing configuration. - """ - console = get_console() - paths = OSAPaths() - - # If no template specified, show available options - if template is None: - console.print("[bold]Available templates:[/bold]\n") - console.print(" [cyan]geo[/cyan] NCBI GEO integration with vector search") - console.print(" [cyan]minimal[/cyan] Blank configuration to customize") - console.print() - console.print("Usage: [bold]osa init