"""Configuration management for PentestGPT using Pydantic.""" from pathlib import Path from typing import Any, Literal from pydantic import Field from pydantic_settings import BaseSettings, SettingsConfigDict class PentestGPTConfig(BaseSettings): """Main configuration for PentestGPT.""" model_config = SettingsConfigDict( env_file=".env", env_file_encoding="utf-8", case_sensitive=False, extra="ignore", ) # LLM Configuration # Note: API key is optional - Claude Code manages its own configuration llm_model: str = Field( default="claude-sonnet-4-5-20250929", description="Claude model to use for the agent" ) llm_api_key: str | None = Field( default=None, description="Optional API key (Claude Code manages its own config)" ) llm_api_base: str | None = Field(default=None, description="Optional custom API base URL") # Agent Configuration max_iterations: int = Field(default=300, description="Maximum iterations for the agent") working_directory: Path = Field( default_factory=lambda: Path.cwd() / "workspace", description="Working directory for agent operations", ) # Target Configuration target: str = Field( ..., # Required description="Target for penetration testing (URL, IP, domain, or path)", ) custom_instruction: str | None = Field( default=None, description="Optional custom instructions for the agent" ) # Interface Configuration interface_mode: Literal["tui", "cli"] = Field( default="tui", description="Interface mode: TUI (interactive) or CLI (headless)" ) verbose: bool = Field(default=True, description="Enable verbose output") # Permission Mode permission_mode: Literal["ask", "bypassPermissions"] = Field( default="bypassPermissions", description="Permission mode for Claude Code SDK" ) def __init__(self, **data: Any) -> None: """Initialize configuration.""" super().__init__(**data) # Create working directory if it doesn't exist # Ignore permission errors if directory already exists try: self.working_directory.mkdir(parents=True, exist_ok=True) except (PermissionError, OSError): # Directory already exists or we don't have permission to create it # This is fine if the directory is already available if not self.working_directory.exists(): raise @property def system_prompt_path(self) -> Path: """Get path to system prompt file.""" return Path(__file__).parent.parent / "prompts" / "pentesting.py" @classmethod def from_env(cls, **overrides: object) -> "PentestGPTConfig": """Create config from environment variables with optional overrides.""" return cls(**overrides) def load_config(**overrides: object) -> PentestGPTConfig: """ Load configuration from environment with optional overrides. Args: **overrides: Keyword arguments to override config values Returns: PentestGPTConfig instance Example: >>> config = load_config(target="example.com", verbose=True) """ # Create config with overrides # Note: API key is optional - Claude Code manages its own configuration return PentestGPTConfig.from_env(**overrides)