Skip to content

API Reference

Dragon edited this page Dec 22, 2025 · 1 revision

🔌 NaviDuck API Reference

Last updated: 12/22/2025

🎯 Overview

This API reference documents the public interfaces for extending NaviDuck. Use these APIs to build plugins, custom search engines, UI themes, and integrations.

┌─────────────────────────────────────────────────────────────┐
│                     Extension Points                         │
├─────────────────────────────────────────────────────────────┤
│  🔍 Search Engines   │  🤖 AI Plugins     │  🎨 UI Themes  │
│  🔧 Core Hooks       │  📡 Network Layer  │  💾 Data Store │
│  🛡️ Security         │  ⚡ Performance    │  🔌 Integrations│
└─────────────────────────────────────────────────────────────┘

🏗️ Core Architecture

BrowserState API

The central state management system that tracks all browser data and configuration.

State Properties

class BrowserState:
    # Configuration
    use_emoji: bool                    # Icon theme preference
    current_engine: str                # Active search engine ID
    tor_enabled: bool                  # Tor status
    
    # Data Stores
    history: List[Dict]                # Browsing history
    bookmarks: List[Dict]              # Saved bookmarks
    current_results: List[Dict]        # Latest search results
    
    # Session State
    current_page: str                  # Current page content
    current_url: str                   # Current URL
    current_title: str                 # Current page title
    
    # File Paths
    data_file: str                     # User data file path
    config_file: str                   # Configuration file path

Public Methods

# Data Management
def load_data(self) -> None:
    """Load user data from disk."""

def save_data(self) -> None:
    """Save user data to disk."""

def load_config(self) -> None:
    """Load configuration from disk."""

def save_config(self) -> None:
    """Save configuration to disk."""

# Bookmark Management
def add_bookmark(self, title: str, url: str) -> bool:
    """Add a bookmark. Returns success status."""

def remove_bookmark(self, url: str) -> bool:
    """Remove a bookmark by URL."""

def get_bookmark(self, url: str) -> Optional[Dict]:
    """Retrieve bookmark by URL."""

# History Management  
def add_history(self, entry: Dict) -> None:
    """Add entry to history."""

def clear_history(self, older_than_days: int = None) -> int:
    """Clear history. Returns number of entries removed."""

# Configuration
def update_config(self, key: str, value: Any) -> None:
    """Update configuration value."""

def get_config(self, key: str, default: Any = None) -> Any:
    """Retrieve configuration value."""

Event System

# Subscribe to state changes
state.add_listener(event_type, callback)

# Event Types
EVENT_SEARCH_COMPLETE = "search_complete"
EVENT_PAGE_LOADED = "page_loaded" 
EVENT_BOOKMARK_ADDED = "bookmark_added"
EVENT_HISTORY_UPDATED = "history_updated"
EVENT_CONFIG_CHANGED = "config_changed"
EVENT_TOR_STATUS_CHANGED = "tor_status_changed"

# Example: Listen for search completion
def on_search_complete(results):
    print(f"Search returned {len(results)} results")

state.add_listener(EVENT_SEARCH_COMPLETE, on_search_complete)

🔍 Search Engine API

SearchEngine Interface

Base class for implementing custom search engines.

class SearchEngine(ABC):
    """Abstract base class for search engines."""
    
    @abstractmethod
    def search(self, query: str) -> List[SearchResult]:
        """Execute search and return results."""
        pass
    
    @abstractmethod
    def parse_response(self, response: Response) -> List[SearchResult]:
        """Parse HTTP response into structured results."""
        pass
    
    @property
    @abstractmethod
    def name(self) -> str:
        """Display name of search engine."""
        pass
    
    @property  
    @abstractmethod
    def icon(self) -> str:
        """Icon identifier for display."""
        pass
    
    @property
    def supports_tor(self) -> bool:
        """Whether engine supports Tor routing."""
        return False
    
    @property
    def rate_limit(self) -> Tuple[int, int]:
        """Rate limit: (requests, seconds)."""
        return (10, 60)

SearchResult Structure

class SearchResult:
    def __init__(
        self,
        title: str,
        url: str,
        snippet: str = "",
        engine: str = "custom",
        metadata: Dict = None
    ):
        self.title = title
        self.url = url
        self.snippet = snippet
        self.engine = engine
        self.metadata = metadata or {}
    
    def to_dict(self) -> Dict:
        """Convert to dictionary format."""
        return {
            "title": self.title[:100],  # Truncated for display
            "url": self.url,
            "snippet": self.snippet[:150],  # Truncated
            "engine": self.engine,
            "metadata": self.metadata
        }

Registering a Search Engine

Method 1: Direct Registration

# Custom search engine implementation
class MySearchEngine(SearchEngine):
    def __init__(self):
        self.base_url = "https://api.example.com/search"
        self.api_key = None
    
    def search(self, query: str) -> List[SearchResult]:
        params = {"q": query, "format": "json"}
        if self.api_key:
            params["key"] = self.api_key
        
        response = requests.get(self.base_url, params=params)
        return self.parse_response(response)
    
    def parse_response(self, response: Response) -> List[SearchResult]:
        data = response.json()
        results = []
        
        for item in data.get("results", []):
            result = SearchResult(
                title=item.get("title", ""),
                url=item.get("url", ""),
                snippet=item.get("description", ""),
                engine="mysearch",
                metadata={
                    "score": item.get("score", 0),
                    "date": item.get("date", "")
                }
            )
            results.append(result)
        
        return results
    
    @property
    def name(self) -> str:
        return "My Search"
    
    @property
    def icon(self) -> str:
        return "MY_ICON"

# Register with SearchManager
search_manager.register_engine("mysearch", MySearchEngine())

Method 2: Configuration-Based

# engines/custom_engines.json
{
  "mysearch": {
    "name": "My Search Engine",
    "url": "https://api.example.com/search",
    "params": {
      "q": "{query}",
      "format": "json",
      "api_key": "${API_KEY}"
    },
    "parser": "json",  # json, html, xml, custom
    "requires_tor": false,
    "icon": "SEARCH",
    "headers": {
      "User-Agent": "NaviDuck/1.0"
    },
    "rate_limit": {
      "requests": 10,
      "seconds": 60
    }
  }
}

# Load configuration
search_manager.load_engines_from_config("engines/custom_engines.json")

Method 3: Decorator Registration

@search_engine(
    name="My Search",
    icon="MY_ICON",
    supports_tor=True
)
def my_search_engine(query: str, context: Dict) -> List[Dict]:
    """Custom search engine function."""
    # Implementation
    return results

# Engine is automatically registered

Search Parser API

For custom HTML/XML parsing logic.

class SearchParser:
    """Base class for custom parsers."""
    
    def parse_html(self, html: str, query: str = "") -> List[SearchResult]:
        """Parse HTML content."""
        # Default implementation using regex
        pattern = r'<a[^>]+href="([^"]+)"[^>]*>([^<]+)</a>'
        matches = re.findall(pattern, html)
        
        results = []
        for url, title in matches[:10]:
            if self._is_valid_result(url, title):
                results.append(SearchResult(
                    title=title.strip(),
                    url=url,
                    snippet=self._extract_snippet(html, title),
                    engine="custom"
                ))
        
        return results
    
    def parse_json(self, data: Dict, query: str = "") -> List[SearchResult]:
        """Parse JSON response."""
        # Implementation depends on API structure
        pass
    
    def _is_valid_result(self, url: str, title: str) -> bool:
        """Filter out invalid/spam results."""
        return (len(title) > 10 and 
                not url.startswith("#") and
                not any(spam in url for spam in self._spam_domains))
    
    def _extract_snippet(self, html: str, title: str) -> str:
        """Extract relevant snippet around title."""
        # Simple implementation
        idx = html.find(title)
        if idx != -1:
            start = max(0, idx - 50)
            end = min(len(html), idx + len(title) + 100)
            return html[start:end]
        return ""

Search Hook System

Intercept and modify search behavior.

class SearchHook:
    """Hook into search process."""
    
    def pre_search(self, query: str, engine: str, context: Dict) -> Optional[Tuple[str, str]]:
        """
        Called before search execution.
        Return (modified_query, modified_engine) to alter search,
        or None to proceed normally.
        """
        # Example: Auto-complete queries
        if len(query) < 3:
            return None  # Don't modify
        
        # Add site: filter for programming queries
        if any(word in query.lower() for word in ["python", "javascript", "code"]):
            return f"{query} site:stackoverflow.com", engine
        
        return None
    
    def post_search(self, query: str, engine: str, results: List[SearchResult]) -> List[SearchResult]:
        """
        Called after search results are retrieved.
        Modify or filter results.
        """
        # Example: Filter out low-quality results
        filtered = [
            r for r in results 
            if self._is_high_quality(r)
        ]
        
        # Example: Add metadata
        for result in filtered:
            result.metadata["processed"] = True
        
        return filtered
    
    def on_error(self, query: str, engine: str, error: Exception) -> Optional[List[SearchResult]]:
        """
        Called when search fails.
        Return fallback results or None to propagate error.
        """
        # Example: Provide cached results
        cached = self._get_cached_results(query)
        if cached:
            return cached
        
        # Example: Try alternative query
        simplified = self._simplify_query(query)
        return search_manager.search(simplified, "ddg")
    
    def _is_high_quality(self, result: SearchResult) -> bool:
        """Quality heuristic."""
        return (
            len(result.title) > 15 and
            len(result.snippet) > 20 and
            "http" in result.url and
            not any(spam in result.url for spam in ["ad", "track", "click"])
        )

Registering Hooks

# Create hook instance
quality_hook = QualityFilterHook()

# Register with search manager
search_manager.add_hook(quality_hook)

# Multiple hooks execute in registration order
search_manager.add_hook(SpamFilterHook())
search_manager.add_hook(ResultRankerHook())

🤖 AI Plugin API

AIPlugin Interface

Extend NavAI with custom knowledge sources and response generators.

class AIPlugin(ABC):
    """Base class for AI plugins."""
    
    def __init__(self, context: Dict = None):
        self.context = context or {}
        self.priority = 50  # 0-100, higher = tried first
    
    @abstractmethod
    def can_handle(self, query: str) -> bool:
        """
        Determine if this plugin can handle the query.
        Return True to attempt processing.
        """
        pass
    
    @abstractmethod
    def process(self, query: str) -> Optional[str]:
        """
        Process query and return response.
        Return None if cannot answer.
        """
        pass
    
    def get_confidence(self, query: str) -> float:
        """
        Confidence score (0-1) for handling this query.
        Used when multiple plugins can handle same query.
        """
        return 0.5
    
    def learn_from_feedback(self, query: str, response: str, was_helpful: bool) -> None:
        """Learn from user feedback."""
        pass

Example: Programming Help Plugin

class ProgrammingHelpPlugin(AIPlugin):
    """Provides programming-related answers."""
    
    def __init__(self):
        super().__init__()
        self.priority = 80  # High priority for programming questions
        self.knowledge_base = self._load_knowledge()
    
    def can_handle(self, query: str) -> bool:
        query_lower = query.lower()
        programming_keywords = [
            "python", "javascript", "java", "code", "programming",
            "function", "class", "variable", "loop", "array",
            "how to", "syntax", "error", "bug", "debug"
        ]
        return any(keyword in query_lower for keyword in programming_keywords)
    
    def process(self, query: str) -> Optional[str]:
        # Check local knowledge base
        answer = self._check_knowledge_base(query)
        if answer:
            return answer
        
        # Fall back to code snippet generation
        if "example" in query.lower() or "code" in query.lower():
            return self._generate_example(query)
        
        return None  # Let other plugins handle
    
    def _load_knowledge(self) -> Dict[str, str]:
        """Load programming knowledge."""
        return {
            "what is a variable": "A variable is a named storage location...",
            "python list comprehension": "List comprehensions provide a concise way...",
            # ... more knowledge
        }
    
    def _check_knowledge_base(self, query: str) -> Optional[str]:
        """Check if query matches known patterns."""
        for pattern, answer in self.knowledge_base.items():
            if pattern in query.lower():
                return answer
        return None
    
    def _generate_example(self, query: str) -> str:
        """Generate code example based on query."""
        if "python" in query.lower():
            return "```python\n# Example: Hello World\nprint('Hello, World!')\n```"
        elif "javascript" in query.lower():
            return "```javascript\n// Example: Hello World\nconsole.log('Hello, World!');\n```"
        return "Here's an example related to your query..."

Example: Weather Plugin

class WeatherPlugin(AIPlugin):
    """Provides weather information."""
    
    def __init__(self, api_key: str = None):
        super().__init__()
        self.api_key = api_key
        self.priority = 70
    
    def can_handle(self, query: str) -> bool:
        weather_terms = ["weather", "temperature", "forecast", "rain", "sunny"]
        return any(term in query.lower() for term in weather_terms)
    
    def process(self, query: str) -> Optional[str]:
        # Extract location
        location = self._extract_location(query)
        if not location:
            return "Please specify a location (e.g., 'weather in London')"
        
        # Get weather data
        weather_data = self._fetch_weather(location)
        if not weather_data:
            return f"Could not fetch weather for {location}"
        
        # Format response
        return self._format_weather_response(weather_data)
    
    def _extract_location(self, query: str) -> Optional[str]:
        """Extract location from query."""
        patterns = [
            r"weather in (.+)",
            r"temperature in (.+)",
            r"forecast for (.+)"
        ]
        
        for pattern in patterns:
            match = re.search(pattern, query.lower())
            if match:
                return match.group(1).strip()
        
        return None
    
    def _fetch_weather(self, location: str) -> Optional[Dict]:
        """Fetch weather data from API."""
        if not self.api_key:
            return None
        
        try:
            response = requests.get(
                "https://api.weatherapi.com/v1/current.json",
                params={
                    "key": self.api_key,
                    "q": location,
                    "aqi": "no"
                }
            )
            return response.json()
        except:
            return None
    
    def _format_weather_response(self, data: Dict) -> str:
        """Format weather data into readable response."""
        location = data["location"]["name"]
        temp_c = data["current"]["temp_c"]
        condition = data["current"]["condition"]["text"]
        
        return f"Weather in {location}: {temp_c}°C, {condition}"

Registering AI Plugins

# Create plugin instances
programming_plugin = ProgrammingHelpPlugin()
weather_plugin = WeatherPlugin(api_key="your_api_key")

# Register with NavAI
navai = NavAI()
navai.register_plugin(programming_plugin)
navai.register_plugin(weather_plugin)

# Or load from configuration
navai.load_plugins_from_config("plugins/ai_plugins.json")

AI Response Formatter API

Customize how AI responses are displayed.

class AIResponseFormatter:
    """Format AI responses for display."""
    
    def format(self, response: str, query: str = "") -> str:
        """Format raw response for display."""
        # Default formatting
        formatted = response.strip()
        
        # Add icons/colors based on content
        if "error" in response.lower():
            formatted = f"❌ {formatted}"
        elif "success" in response.lower():
            formatted = f"✅ {formatted}"
        
        # Wrap long lines
        formatted = self._wrap_text(formatted, width=70)
        
        return formatted
    
    def format_with_context(self, response: str, context: Dict) -> str:
        """Format response with additional context."""
        # Add source attribution
        if context.get("source"):
            response = f"{response}\n\nSource: {context['source']}"
        
        # Add confidence score
        if confidence := context.get("confidence"):
            response = f"{response}\n\nConfidence: {confidence:.0%}"
        
        return self.format(response)
    
    def _wrap_text(self, text: str, width: int) -> str:
        """Wrap text to specified width."""
        import textwrap
        return "\n".join(textwrap.wrap(text, width=width))

🎨 UI Theme API

Theme Interface

Create custom color schemes and icon sets.

class Theme:
    """UI theme with colors and icons."""
    
    def __init__(self, name: str):
        self.name = name
        self.colors = self._default_colors()
        self.icons = self._default_icons()
    
    def _default_colors(self) -> Dict[str, str]:
        """Default color scheme."""
        return {
            # Text colors
            "text.primary": "\033[37m",      # White
            "text.secondary": "\033[90m",    # Gray
            "text.success": "\033[32m",      # Green
            "text.error": "\033[31m",        # Red
            "text.warning": "\033[33m",      # Yellow
            "text.info": "\033[36m",         # Cyan
            "text.highlight": "\033[35m",    # Magenta
            
            # Background colors
            "bg.primary": "\033[40m",        # Black background
            "bg.secondary": "\033[47m",      # White background
            "bg.highlight": "\033[45m",      # Magenta background
            
            # Special
            "reset": "\033[0m",              # Reset all
            "bold": "\033[1m",               # Bold
            "underline": "\033[4m",          # Underline
        }
    
    def _default_icons(self) -> Dict[str, str]:
        """Default icon set."""
        return {
            "search": "🔍",
            "ai": "🤖",
            "bookmark": "📑",
            "history": "🕰️",
            "tor": "🧅",
            "settings": "⚙️",
            "error": "❌",
            "success": "✅",
            "warning": "⚠️",
            "info": "ℹ️",
        }
    
    def apply(self) -> None:
        """Apply theme to UI."""
        UIManager.current_theme = self
    
    def get_color(self, key: str, default: str = "") -> str:
        """Get color code by key."""
        return self.colors.get(key, default)
    
    def get_icon(self, key: str, default: str = "?") -> str:
        """Get icon by key."""
        return self.icons.get(key, default)

Creating Custom Themes

Dark Theme

class DarkTheme(Theme):
    """Dark mode theme."""
    
    def __init__(self):
        super().__init__("dark")
        self.colors.update({
            "text.primary": "\033[37m",      # Bright white
            "text.secondary": "\033[90m",    # Dark gray
            "bg.primary": "\033[40m",        # Black
            "bg.secondary": "\033[48;5;236m",# Dark gray
        })
        self.icons.update({
            "search": "󰍉",  # Nerd Font icons
            "ai": "󰚩",
            "bookmark": "󰆿",
        })

Light Theme

class LightTheme(Theme):
    """Light mode theme."""
    
    def __init__(self):
        super().__init__("light")
        self.colors.update({
            "text.primary": "\033[30m",      # Black
            "text.secondary": "\033[90m",    # Dark gray
            "bg.primary": "\033[47m",        # White
            "bg.secondary": "\033[48;5;255m",# Light gray
        })

High Contrast Theme

class HighContrastTheme(Theme):
    """High contrast theme for accessibility."""
    
    def __init__(self):
        super().__init__("high-contrast")
        self.colors.update({
            "text.primary": "\033[97m",      # Bright white
            "text.secondary": "\033[37m",    # White
            "text.success": "\033[92m",      # Bright green
            "text.error": "\033[91m",        # Bright red
            "bg.primary": "\033[40m",        # Black
            "bg.secondary": "\033[100m",     # Bright black
        })
        self.icons.update({
            "search": "🔍",
            "ai": "🤖",
            # Larger, clearer icons
        })

Icon Set API

class IconSet:
    """Collection of icons for different contexts."""
    
    def __init__(self, name: str):
        self.name = name
        self.icons = {}
    
    def add_icon(self, key: str, icon: str, variants: Dict[str, str] = None):
        """Add icon with optional variants."""
        self.icons[key] = {
            "default": icon,
            "variants": variants or {}
        }
    
    def get_icon(self, key: str, variant: str = "default") -> str:
        """Get icon by key and variant."""
        if key not in self.icons:
            return "?"
        
        icon_data = self.icons[key]
        return icon_data["variants"].get(variant, icon_data["default"])
    
    def get_icon_for_context(self, key: str, context: Dict) -> str:
        """Get icon based on context (theme, platform, etc.)."""
        icon = self.get_icon(key)
        
        # Adjust based on context
        if context.get("theme") == "dark":
            # Use brighter icons for dark themes
            pass
        
        if context.get("platform") == "windows":
            # Use Windows-friendly icons
            pass
        
        return icon

Example: Nerd Font Icon Set

class NerdFontIconSet(IconSet):
    """Nerd Font icon set."""
    
    def __init__(self):
        super().__init__("nerd-font")
        self.add_icon("search", "󰍉")
        self.add_icon("ai", "󰚩")
        self.add_icon("bookmark", "󰆿")
        self.add_icon("history", "󰋚")
        self.add_icon("tor", "󰙭")
        self.add_icon("settings", "󰒓")
        self.add_icon("error", "󰀦")
        self.add_icon("success", "󰱢")
        self.add_icon("warning", "󰀪")
        self.add_icon("info", "󰋼")

Example: Emoji Icon Set

class EmojiIconSet(IconSet):
    """Emoji icon set."""
    
    def __init__(self):
        super().__init__("emoji")
        self.add_icon("search", "🔍")
        self.add_icon("ai", "🤖")
        self.add_icon("bookmark", "📑")
        self.add_icon("history", "🕰️")
        self.add_icon("tor", "🧅")
        self.add_icon("settings", "⚙️")
        self.add_icon("error", "❌")
        self.add_icon("success", "✅")
        self.add_icon("warning", "⚠️")
        self.add_icon("info", "ℹ️")

Registering Themes

# Create themes
dark_theme = DarkTheme()
light_theme = LightTheme()
high_contrast = HighContrastTheme()

# Register with theme manager
theme_manager = ThemeManager()
theme_manager.register_theme(dark_theme)
theme_manager.register_theme(light_theme)
theme_manager.register_theme(high_contrast)

# Apply theme
theme_manager.apply_theme("dark")

# Or load from configuration
theme_manager.load_themes_from_dir("themes/")

🔧 Core Hook System

Hook Interface

Hook into various points in NaviDuck's execution.

class Hook:
    """Base class for all hooks."""
    
    def __init__(self, name: str, priority: int = 50):
        self.name = name
        self.priority = priority  # Execution order: 0-100
    
    def get_hook_points(self) -> List[str]:
        """
        Return list of hook points this hook subscribes to.
        Example: ["pre_command", "post_search", "on_error"]
        """
        return []

Available Hook Points

# Command Processing
HOOK_PRE_COMMAND = "pre_command"        # Before command execution
HOOK_POST_COMMAND = "post_command"      # After command execution
HOOK_COMMAND_ERROR = "command_error"    # On command error

# Search Process
HOOK_PRE_SEARCH = "pre_search"          # Before search
HOOK_POST_SEARCH = "post_search"        # After search
HOOK_SEARCH_ERROR = "search_error"      # On search error

# Page Loading
HOOK_PRE_PAGE_LOAD = "pre_page_load"    # Before page load
HOOK_POST_PAGE_LOAD = "post_page_load"  # After page load
HOOK_PAGE_ERROR = "page_error"          # On page load error

# AI Processing
HOOK_PRE_AI_QUERY = "pre_ai_query"      # Before AI query
HOOK_POST_AI_RESPONSE = "post_ai_response"  # After AI response
HOOK_AI_ERROR = "ai_error"              # On AI error

# Data Management
HOOK_PRE_DATA_SAVE = "pre_data_save"    # Before saving data
HOOK_POST_DATA_LOAD = "post_data_load"  # After loading data

# UI Events
HOOK_PRE_UI_RENDER = "pre_ui_render"    # Before UI render
HOOK_POST_UI_RENDER = "post_ui_render"  # After UI render

# System Events
HOOK_STARTUP = "startup"                # On startup
HOOK_SHUTDOWN = "shutdown"              # On shutdown
HOOK_ERROR = "error"                    # Any error

Example: Analytics Hook

class AnalyticsHook(Hook):
    """Track usage analytics."""
    
    def __init__(self):
        super().__init__("analytics", priority=10)
        self.events = []
    
    def get_hook_points(self) -> List[str]:
        return [
            HOOK_PRE_COMMAND,
            HOOK_POST_SEARCH,
            HOOK_POST_AI_RESPONSE,
            HOOK_ERROR
        ]
    
    def on_pre_command(self, command: str, args: List[str]) -> None:
        """Track command execution."""
        self.events.append({
            "timestamp": time.time(),
            "type": "command",
            "command": command,
            "args": args
        })
    
    def on_post_search(self, query: str, engine: str, results_count: int) -> None:
        """Track search performance."""
        self.events.append({
            "timestamp": time.time(),
            "type": "search",
            "query": query,
            "engine": engine,
            "results": results_count
        })
    
    def on_error(self, error: Exception, context: Dict) -> None:
        """Track errors."""
        self.events.append({
            "timestamp": time.time(),
            "type": "error",
            "error": str(error),
            "context": context
        })
    
    def save_analytics(self) -> None:
        """Save analytics data."""
        import json
        with open("analytics.json", "w") as f:
            json.dump(self.events, f, indent=2)

Example: Security Hook

class SecurityHook(Hook):
    """Security monitoring and validation."""
    
    def __init__(self):
        super().__init__("security", priority=5)  # High priority
    
    def get_hook_points(self) -> List[str]:
        return [
            HOOK_PRE_COMMAND,
            HOOK_PRE_PAGE_LOAD,
            HOOK_PRE_DATA_SAVE
        ]
    
    def on_pre_command(self, command: str, args: List[str]) -> Optional[Tuple[str, List[str]]]:
        """Validate and sanitize commands."""
        # Block dangerous commands
        dangerous = ["rm", "format", "delete"]
        if any(cmd in command.lower() for cmd in dangerous):
            raise SecurityError(f"Blocked dangerous command: {command}")
        
        # Sanitize arguments
        sanitized_args = [self._sanitize(arg) for arg in args]
        
        return command, sanitized_args
    
    def on_pre_page_load(self, url: str) -> Optional[str]:
        """Validate URLs before loading."""
        # Check for malicious patterns
        if self._is_malicious_url(url):
            raise SecurityError(f"Blocked malicious URL: {url}")
        
        # Enforce HTTPS for sensitive sites
        if "login" in url or "bank" in url:
            if not url.startswith("https://"):
                url = url.replace("http://", "https://")
        
        return url
    
    def _sanitize(self, text: str) -> str:
        """Sanitize input text."""
        # Remove control characters
        return ''.join(char for char in text if ord(char) >= 32)
    
    def _is_malicious_url(self, url: str) -> bool:
        """Check if URL is potentially malicious."""
        malicious_patterns = [
            r"javascript:",
            r"data:text/html",
            r"file://",
            r"\.exe$",
            r"\.bat$",
            r"\.sh$"
        ]
        
        for pattern in malicious_patterns:
            if re.search(pattern, url, re.IGNORECASE):
                return True
        
        return False

Registering Hooks

# Create hook instances
analytics_hook = AnalyticsHook()
security_hook = SecurityHook()
logging_hook = LoggingHook()

# Register with hook manager
hook_manager = HookManager()
hook_manager.register_hook(analytics_hook)
hook_manager.register_hook(security_hook)
hook_manager.register_hook(logging_hook)

# Hooks execute in priority order (lowest first)
# Security (5) → Analytics (10) → Logging (50)

📡 Network Layer API

Custom HTTP Adapters

class CustomHTTPAdapter(requests.adapters.HTTPAdapter):
    """Custom HTTP adapter with additional features."""
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.request_hooks = []
        self.response_hooks = []
    
    def send(self, request, **kwargs):
        """Override send with custom logic."""
        
        # Pre-request hooks
        for hook in self.request_hooks:
            request = hook(request)
        
        # Add custom headers
        request.headers.update({
            "X-NaviDuck-Version": "1.0",
            "X-Request-ID": str(uuid.uuid4())
        })
        
        # Execute request
        response = super().send(request, **kwargs)
        
        # Post-response hooks
        for hook in self.response_hooks:
            response = hook(response)
        
        return response
    
    def add_request_hook(self, hook: Callable):
        """Add hook to modify requests."""
        self.request_hooks.append(hook)
    
    def add_response_hook(self, hook: Callable):
        """Add hook to modify responses."""
        self.response_hooks.append(hook)

Proxy Configuration

class ProxyManager:
    """Manage proxy configurations."""
    
    def __init__(self):
        self.proxies = {}
        self.current_proxy = None
    
    def add_proxy(self, name: str, config: Dict):
        """Add proxy configuration."""
        self.proxies[name] = {
            "http": config.get("http"),
            "https": config.get("https"),
            "socks": config.get("socks"),
            "auth": config.get("auth"),  # (username, password)
            "enabled": config.get("enabled", True)
        }
    
    def set_proxy(self, name: str) -> bool:
        """Set active proxy."""
        if name not in self.proxies:
            return False
        
        proxy_config = self.proxies[name]
        if not proxy_config["enabled"]:
            return False
        
        self.current_proxy = proxy_config
        return True
    
    def get_proxy_config(self) -> Dict:
        """Get current proxy configuration for requests."""
        if not self.current_proxy:
            return {}
        
        config = {}
        proxy = self.current_proxy
        
        if proxy.get("http"):
            config["http"] = self._format_proxy_url("http", proxy)
        if proxy.get("https"):
            config["https"] = self._format_proxy_url("https", proxy)
        if proxy.get("socks"):
            config["http"] = proxy["socks"]
            config["https"] = proxy["socks"]
        
        return config
    
    def _format_proxy_url(self, scheme: str, proxy: Dict) -> str:
        """Format proxy URL with authentication."""
        url = proxy[scheme]
        if auth := proxy.get("auth"):
            username, password = auth
            url = url.replace("://", f"://{username}:{password}@")
        return url

SSL/TLS Configuration

class SSLConfig:
    """Configure SSL/TLS settings."""
    
    def __init__(self):
        self.verify = True
        self.cert = None
        self.ciphers = None
        self.min_version = None
        self.max_version = None
        self.ca_cert = None
    
    def create_context(self) -> ssl.SSLContext:
        """Create SSL context with configuration."""
        context = ssl.create_default_context()
        
        if self.ciphers:
            context.set_ciphers(self.ciphers)
        
        if self.min_version:
            context.minimum_version = self.min_version
        
        if self.max_version:
            context.maximum_version = self.max_version
        
        if self.ca_cert:
            context.load_verify_locations(self.ca_cert)
        
        return context
    
    def disable_verification(self):
        """Disable SSL verification (not recommended for production)."""
        self.verify = False
        import urllib3
        urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

Registering Network Components

# Create network components
proxy_manager = ProxyManager()
ssl_config = SSLConfig()
adapter = CustomHTTPAdapter()

# Configure
proxy_manager.add_proxy("tor", {
    "socks": "socks5h://127.0.0.1:9050",
    "enabled": True
})

ssl_config.ciphers = "ECDHE+AESGCM:ECDHE+CHACHA20"
ssl_config.min_version = ssl.TLSVersion.TLSv1_2

# Apply to session
session = requests.Session()
session.mount("http://", adapter)
session.mount("https://", adapter)

# Set proxy
session.proxies = proxy_manager.get_proxy_config()
session.verify = ssl_config.create_context()

💾 Data Store API

Storage Backend Interface

class StorageBackend(ABC):
    """Abstract storage backend."""
    
    @abstractmethod
    def save(self, key: str, data: Any) -> bool:
        """Save data with key."""
        pass
    
    @abstractmethod
    def load(self, key: str) -> Any:
        """Load data by key."""
        pass
    
    @abstractmethod
    def delete(self, key: str) -> bool:
        """Delete data by key."""
        pass
    
    @abstractmethod
    def exists(self, key: str) -> bool:
        """Check if key exists."""
        pass
    
    @abstractmethod
    def list_keys(self, pattern: str = "*") -> List[str]:
        """List keys matching pattern."""
        pass

JSON File Backend

class JSONStorageBackend(StorageBackend):
    """JSON file storage backend."""
    
    def __init__(self, base_path: str = "~/.naviduck"):
        self.base_path = os.path.expanduser(base_path)
        os.makedirs(self.base_path, exist_ok=True)
    
    def save(self, key: str, data: Any) -> bool:
        """Save data as JSON."""
        try:
            filepath = self._get_filepath(key)
            with open(filepath, "w", encoding="utf-8") as f:
                json.dump(data, f, indent=2, ensure_ascii=False)
            return True
        except:
            return False
    
    def load(self, key: str) -> Any:
        """Load data from JSON."""
        try:
            filepath = self._get_filepath(key)
            if not os.path.exists(filepath):
                return None
            
            with open(filepath, "r", encoding="utf-8") as f:
                return json.load(f)
        except:
            return None
    
    def _get_filepath(self, key: str) -> str:
        """Convert key to filepath."""
        # Sanitize key for filename
        safe_key = "".join(c for c in key if c.isalnum() or c in "._-")
        return os.path.join(self.base_path, f"{safe_key}.json")

SQLite Backend

class SQLiteStorageBackend(StorageBackend):
    """SQLite storage backend."""
    
    def __init__(self, db_path: str = "~/.naviduck/data.db"):
        self.db_path = os.path.expanduser(db_path)
        self._init_db()
    
    def _init_db(self):
        """Initialize database schema."""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS storage (
                key TEXT PRIMARY KEY,
                value TEXT,
                created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
            )
        """)
        
        conn.commit()
        conn.close()
    
    def save(self, key: str, data: Any) -> bool:
        """Save data to SQLite."""
        try:
            conn = sqlite3.connect(self.db_path)
            cursor = conn.cursor()
            
            value = json.dumps(data)
            cursor.execute("""
                INSERT OR REPLACE INTO storage (key, value, updated)
                VALUES (?, ?, CURRENT_TIMESTAMP)
            """, (key, value))
            
            conn.commit()
            conn.close()
            return True
        except:
            return False

Encrypted Storage

class EncryptedStorageBackend(StorageBackend):
    """Encrypted storage backend."""
    
    def __init__(self, backend: StorageBackend, encryption_key: bytes):
        self.backend = backend
        self.cipher = Fernet(encryption_key)
    
    def save(self, key: str, data: Any) -> bool:
        """Save encrypted data."""
        try:
            # Serialize and encrypt
            serialized = json.dumps(data).encode()
            encrypted = self.cipher.encrypt(serialized)
            
            # Save through backend
            return self.backend.save(key, encrypted.hex())
        except:
            return False
    
    def load(self, key: str) -> Any:
        """Load and decrypt data."""
        try:
            # Load encrypted data
            encrypted_hex = self.backend.load(key)
            if not encrypted_hex:
                return None
            
            # Decrypt and deserialize
            encrypted = bytes.fromhex(encrypted_hex)
            decrypted = self.cipher.decrypt(encrypted)
            return json.loads(decrypted.decode())
        except:
            return None

Caching Layer

class CachedStorageBackend(StorageBackend):
    """Storage backend with caching."""
    
    def __init__(self, backend: StorageBackend, max_cache_size: int = 1000):
        self.backend = backend
        self.cache = {}
        self.cache_order = []  # For LRU
        self.max_cache_size = max_cache_size
    
    def save(self, key: str, data: Any) -> bool:
        """Save to cache and backend."""
        # Update cache
        self._update_cache(key, data)
        
        # Save to backend
        return self.backend.save(key, data)
    
    def load(self, key: str) -> Any:
        """Load from cache if available."""
        # Check cache first
        if key in self.cache:
            # Move to front (most recently used)
            self.cache_order.remove(key)
            self.cache_order.insert(0, key)
            return self.cache[key]
        
        # Load from backend
        data = self.backend.load(key)
        if data is not None:
            self._update_cache(key, data)
        
        return data
    
    def _update_cache(self, key: str, data: Any):
        """Update cache with LRU eviction."""
        if key in self.cache:
            self.cache_order.remove(key)
        elif len(self.cache) >= self.max_cache_size:
            # Evict least recently used
            lru_key = self.cache_order.pop()
            del self.cache[lru_key]
        
        self.cache[key] = data
        self.cache_order.insert(0, key)

Using Storage Backends

# Create backend chain
json_backend = JSONStorageBackend()
encrypted_backend = EncryptedStorageBackend(json_backend, encryption_key)
cached_backend = CachedStorageBackend(encrypted_backend)

# Use with BrowserState
state = BrowserState(storage_backend=cached_backend)

# Or register multiple backends
storage_manager = StorageManager()
storage_manager.register_backend("json", json_backend)
storage_manager.register_backend("encrypted", encrypted_backend)
storage_manager.register_backend("cached", cached_backend)

# Use specific backend
storage_manager.use_backend("cached")
storage_manager.save("history", history_data)

🛡️ Security API

Input Validator

class InputValidator:
    """Validate and sanitize user input."""
    
    def validate_search_query(self, query: str) -> Tuple[bool, str]:
        """Validate search query."""
        if not query or not query.strip():
            return False, "Query cannot be empty"
        
        if len(query) > 500:
            return False, "Query too long (max 500 characters)"
        
        # Check for dangerous patterns
        dangerous = ["<script>", "javascript:", "onload="]
        for pattern in dangerous:
            if pattern in query.lower():
                return False, "Query contains unsafe content"
        
        return True, query.strip()
    
    def validate_url(self, url: str) -> Tuple[bool, str]:
        """Validate URL."""
        try:
            result = urlparse(url)
            
            # Check scheme
            if result.scheme not in ["http", "https", "ftp"]:
                return False, "Unsupported URL scheme"
            
            # Check for localhost/internal
            if self._is_local_address(result.netloc):
                return False, "Local addresses not allowed"
            
            # Check for malicious patterns
            if self._is_malicious_url(url):
                return False, "URL appears malicious"
            
            return True, url
        except:
            return False, "Invalid URL format"
    
    def sanitize_html(self, html: str) -> str:
        """Sanitize HTML content."""
        # Remove scripts
        html = re.sub(r'<script[^>]*>.*?</script>', '', html, flags=re.DOTALL)
        
        # Remove event handlers
        html = re.sub(r'on\w+="[^"]*"', '', html)
        html = re.sub(r"on\w+='[^']*'", '', html)
        
        # Remove dangerous protocols
        html = re.sub(r'javascript:', '', html, flags=re.IGNORECASE)
        html = re.sub(r'data:', '', html, flags=re.IGNORECASE)
        
        return html

Rate Limiter

class RateLimiter:
    """Rate limiting for API calls."""
    
    def __init__(self, max_requests: int, period: int):
        self.max_requests = max_requests
        self.period = period
        self.requests = defaultdict(list)
    
    def check(self, key: str) -> Tuple[bool, float]:
        """
        Check if request is allowed.
        Returns (allowed, wait_time)
        """
        now = time.time()
        
        # Clean old requests
        self.requests[key] = [
            timestamp for timestamp in self.requests[key]
            if now - timestamp < self.period
        ]
        
        # Check limit
        if len(self.requests[key]) >= self.max_requests:
            oldest = self.requests[key][0]
            wait_time = self.period - (now - oldest)
            return False, wait_time
        
        # Add request
        self.requests[key].append(now)
        return True, 0
    
    def get_remaining(self, key: str) -> int:
        """Get remaining requests in period."""
        now = time.time()
        self.requests[key] = [
            timestamp for timestamp in self.requests[key]
            if now - timestamp < self.period
        ]
        return self.max_requests - len(self.requests[key])

Certificate Pinning

class CertificatePinner:
    """SSL certificate pinning."""
    
    def __init__(self):
        self.pins = {}  # hostname -> [sha256 hashes]
    
    def add_pin(self, hostname: str, certificate_hash: str):
        """Add certificate pin for hostname."""
        if hostname not in self.pins:
            self.pins[hostname] = []
        self.pins[hostname].append(certificate_hash)
    
    def verify(self, hostname: str, certificate: bytes) -> bool:
        """Verify certificate matches pinned hash."""
        if hostname not in self.pins:
            return True  # No pin, allow
        
        # Calculate hash
        cert_hash = hashlib.sha256(certificate).digest()
        b64_hash = base64.b64encode(cert_hash).decode()
        
        # Check against pins
        return b64_hash in self.pins[hostname]
    
    def create_adapter(self) -> requests.adapters.HTTPAdapter:
        """Create HTTP adapter with pinning."""
        class PinningAdapter(requests.adapters.HTTPAdapter):
            def __init__(self, pinner):
                super().__init__()
                self.pinner = pinner
            
            def cert_verify(self, conn, url, verify, cert):
                super().cert_verify(conn, url, verify, cert)
                
                hostname = urlparse(url).hostname
                peer_cert = conn.sock.getpeercert(binary_form=True)
                
                if not self.pinner.verify(hostname, peer_cert):
                    raise requests.exceptions.SSLError(
                        f"Certificate pinning violation for {hostname}"
                    )
        
        return PinningAdapter(self)

Using Security Components

# Setup security
validator = InputValidator()
rate_limiter = RateLimiter(max_requests=10, period=60)
cert_pinner = CertificatePinner()

# Add certificate pins
cert_pinner.add_pin("api.duckduckgo.com", "sha256/...")
cert_pinner.add_pin("wikipedia.org", "sha256/...")

# Create secure session
session = requests.Session()
session.mount("https://", cert_pinner.create_adapter())

# Validate input
is_valid, message = validator.validate_search_query(user_input)
if not is_valid:
    print(f"Invalid input: {message}")

⚡ Performance API

Profiler

class Profiler:
    """Performance profiling."""
    
    def __init__(self):
        self.measurements = {}
        self.start_times = {}
    
    def start(self, name: str):
        """Start measurement."""
        self.start_times[name] = time.perf_counter()
    
    def stop(self, name: str) -> float:
        """Stop measurement and return duration."""
        if name not in self.start_times:
            return 0.0
        
        duration = time.perf_counter() - self.start_times[name]
        
        if name not in self.measurements:
            self.measurements[name] = []
        
        self.measurements[name].append(duration)
        return duration
    
    def get_stats(self, name: str) -> Dict:
        """Get statistics for measurement."""
        if name not in self.measurements:
            return {}
        
        times = self.measurements[name]
        return {
            "count": len(times),
            "total": sum(times),
            "avg": sum(times) / len(times),
            "min": min(times),
            "max": max(times),
            "last": times[-1] if times else 0
        }
    
    def print_report(self):
        """Print performance report."""
        print("Performance Report:")
        print("=" * 50)
        
        for name in sorted(self.measurements.keys()):
            stats = self.get_stats(name)
            print(f"{name}:")
            print(f"  Count: {stats['count']}")
            print(f"  Average: {stats['avg']:.3f}s")
            print(f"  Min/Max: {stats['min']:.3f}s / {stats['max']:.3f}s")
            print()

Cache Manager

class CacheManager:
    """Unified cache management."""
    
    def __init__(self):
        self.caches = {}
    
    def register_cache(self, name: str, cache: Any):
        """Register cache instance."""
        self.caches[name] = cache
    
    def get_cache(self, name: str) -> Optional[Any]:
        """Get cache by name."""
        return self.caches.get(name)
    
    def clear_all(self):
        """Clear all caches."""
        for cache in self.caches.values():
            if hasattr(cache, "clear"):
                cache.clear()
    
    def get_stats(self) -> Dict:
        """Get cache statistics."""
        stats = {}
        for name, cache in self.caches.items():
            if hasattr(cache, "get_stats"):
                stats[name] = cache.get_stats()
            else:
                stats[name] = {"size": len(cache) if hasattr(cache, "__len__") else "unknown"}
        return stats

Memory Monitor

class MemoryMonitor:
    """Monitor memory usage."""
    
    def __init__(self):
        self.samples = []
        self.max_samples = 100
    
    def sample(self) -> float:
        """Take memory sample."""
        import psutil
        import os
        
        process = psutil.Process(os.getpid())
        memory_mb = process.memory_info().rss / 1024 / 1024
        
        self.samples.append(memory_mb)
        if len(self.samples) > self.max_samples:
            self.samples.pop(0)
        
        return memory_mb
    
    def get_stats(self) -> Dict:
        """Get memory statistics."""
        if not self.samples:
            return {}
        
        return {
            "current": self.samples[-1],
            "average": sum(self.samples) / len(self.samples),
            "min": min(self.samples),
            "max": max(self.samples),
            "trend": self._calculate_trend()
        }
    
    def _calculate_trend(self) -> str:
        """Calculate memory trend."""
        if len(self.samples) < 10:
            return "unknown"
        
        recent = self.samples[-5:]
        older = self.samples[-10:-5]
        
        avg_recent = sum(recent) / len(recent)
        avg_older = sum(older) / len(older)
        
        if avg_recent > avg_older * 1.1:
            return "increasing"
        elif avg_recent < avg_older * 0.9:
            return "decreasing"
        else:
            return "stable"
    
    def check_for_leak(self) -> bool:
        """Check for potential memory leak."""
        if len(self.samples) < 20:
            return False
        
        # Check if memory consistently increasing
        segments = [
            self.samples[:5],
            self.samples[5:10],
            self.samples[10:15],
            self.samples[15:]
        ]
        
        averages = [sum(seg) / len(seg) for seg in segments]
        
        # Check if each segment is larger than previous
        for i in range(1, len(averages)):
            if averages[i] < averages[i-1] * 1.05:  # Less than 5% increase
                return False
        
        return True

Using Performance Tools

# Setup performance monitoring
profiler = Profiler()
cache_manager = CacheManager()
memory_monitor = MemoryMonitor()

# Profile operations
profiler.start("search")
results = search_manager.search("test")
profiler.stop("search")

# Monitor memory
memory_monitor.sample()

# Check for issues
if memory_monitor.check_for_leak():
    print("⚠️ Potential memory leak detected!")

# Get performance report
profiler.print_report()
print(f"Memory: {memory_monitor.get_stats()}")

🔌 Integration API

Webhook Support

class WebhookManager:
    """Manage webhook integrations."""
    
    def __init__(self):
        self.webhooks = {}
    
    def register_webhook(self, event: str, url: str, secret: str = None):
        """Register webhook for event."""
        if event not in self.webhooks:
            self.webhooks[event] = []
        
        self.webhooks[event].append({
            "url": url,
            "secret": secret,
            "enabled": True
        })
    
    def trigger(self, event: str, data: Dict):
        """Trigger webhooks for event."""
        if event not in self.webhooks:
            return
        
        for webhook in self.webhooks[event]:
            if not webhook["enabled"]:
                continue
            
            self._send_webhook(webhook, event, data)
    
    def _send_webhook(self, webhook: Dict, event: str, data: Dict):
        """Send webhook request."""
        payload = {
            "event": event,
            "timestamp": time.time(),
            "data": data
        }
        
        headers = {
            "User-Agent": "NaviDuck/1.0",
            "Content-Type": "application/json"
        }
        
        # Add signature if secret provided
        if webhook["secret"]:
            import hmac
            import hashlib
            
            payload_str = json.dumps(payload)
            signature = hmac.new(
                webhook["secret"].encode(),
                payload_str.encode(),
                hashlib.sha256
            ).hexdigest()
            
            headers["X-NaviDuck-Signature"] = signature
        
        try:
            response = requests.post(
                webhook["url"],
                json=payload,
                headers=headers,
                timeout=5
            )
            response.raise_for_status()
        except:
            pass  # Log error but don't crash

CLI Integration

class CLIWrapper:
    """Wrap NaviDuck for CLI integration."""
    
    def __init__(self, naviduck_instance):
        self.naviduck = naviduck_instance
    
    def execute_command(self, command: str) -> str:
        """Execute command and return output."""
        import io
        import sys
        
        # Capture output
        old_stdout = sys.stdout
        sys.stdout = io.StringIO()
        
        try:
            # Execute command
            self.naviduck.handle_command(command)
            
            # Get output
            output = sys.stdout.getvalue()
            return output.strip()
        finally:
            sys.stdout = old_stdout
    
    def search_and_return(self, query: str, engine: str = None) -> List[Dict]:
        """Search and return results as data."""
        results = self.naviduck.search_manager.search(query, engine)
        return [r.to_dict() for r in results]
    
    def ask_ai(self, question: str) -> str:
        """Ask AI and return response."""
        return self.naviduck.navai.ask(question)

REST API Server (Planned)

class RESTServer:
    """REST API server for NaviDuck."""
    
    def __init__(self, naviduck_instance, host="127.0.0.1", port=8080):
        self.naviduck = naviduck_instance
        self.host = host
        self.port = port
        
        from flask import Flask, request, jsonify
        self.app = Flask(__name__)
        self._setup_routes()
    
    def _setup_routes(self):
        """Setup REST API routes."""
        
        @self.app.route("/api/search", methods=["POST"])
        def search():
            data = request.json
            results = self.naviduck.search_manager.search(
                data["query"],
                data.get("engine")
            )
            return jsonify([r.to_dict() for r in results])
        
        @self.app.route("/api/ai", methods=["POST"])
        def ai():
            data = request.json
            response = self.naviduck.navai.ask(data["query"])
            return jsonify({"response": response})
        
        @self.app.route("/api/bookmarks", methods=["GET", "POST"])
        def bookmarks():
            if request.method == "GET":
                return jsonify(self.naviduck.state.bookmarks)
            else:
                data = request.json
                self.naviduck.state.add_bookmark(
                    data["title"],
                    data["url"]
                )
                return jsonify({"success": True})
    
    def start(self):
        """Start REST server."""
        self.app.run(host=self.host, port=self.port)

📦 Plugin System

Plugin Manifest

{
  "name": "my-plugin",
  "version": "1.0.0",
  "description": "My custom plugin",
  "author": "Your Name",
  "license": "MIT",
  
  "entry_points": {
    "search_engines": "my_plugin.search_engine:MySearchEngine",
    "ai_plugins": "my_plugin.ai_plugin:MyAIPlugin",
    "themes": "my_plugin.theme:MyTheme",
    "hooks": "my_plugin.hooks:MyHook"
  },
  
  "dependencies": [
    "requests>=2.25.0",
    "beautifulsoup4>=4.9.0"
  ],
  
  "config_schema": {
    "api_key": {
      "type": "string",
      "description": "API key for the service",
      "required": false
    },
    "enabled": {
      "type": "boolean",
      "default": true,
      "description": "Enable the plugin"
    }
  }
}

Plugin Loader

class PluginLoader:
    """Load and manage plugins."""
    
    def __init__(self):
        self.plugins = {}
        self.loaded_plugins = {}
    
    def load_plugin(self, path: str) -> bool:
        """Load plugin from path."""
        try:
            # Read manifest
            manifest_path = os.path.join(path, "plugin.json")
            with open(manifest_path, "r") as f:
                manifest = json.load(f)
            
            # Load entry points
            for entry_type, entry_point in manifest["entry_points"].items():
                self._load_entry_point(manifest, entry_type, entry_point)
            
            # Store plugin
            plugin_id = f"{manifest['name']}@{manifest['version']}"
            self.loaded_plugins[plugin_id] = {
                "manifest": manifest,
                "path": path,
                "loaded_at": time.time()
            }
            
            return True
        except Exception as e:
            print(f"Failed to load plugin {path}: {e}")
            return False
    
    def _load_entry_point(self, manifest: Dict, entry_type: str, entry_point: str):
        """Load specific entry point."""
        module_name, class_name = entry_point.split(":")
        
        # Import module
        import importlib
        module = importlib.import_module(module_name)
        
        # Get class
        plugin_class = getattr(module, class_name)
        
        # Instantiate with config
        config = manifest.get("config", {})
        plugin_instance = plugin_class(**config)
        
        # Register based on type
        if entry_type == "search_engines":
            search_manager.register_engine(
                manifest["name"],
                plugin_instance
            )
        elif entry_type == "ai_plugins":
            navai.register_plugin(plugin_instance)
        elif entry_type == "themes":
            theme_manager.register_theme(plugin_instance)
        elif entry_type == "hooks":
            hook_manager.register_hook(plugin_instance)
    
    def list_plugins(self) -> List[Dict]:
        """List loaded plugins."""
        return [
            {
                "id": plugin_id,
                "name": info["manifest"]["name"],
                "version": info["manifest"]["version"],
                "loaded_at": info["loaded_at"]
            }
            for plugin_id, info in self.loaded_plugins.items()
        ]

🚀 Getting Started with Extensions

Minimal Search Engine Extension

# my_search_engine.py
from naviduck.api import SearchEngine, SearchResult

class MySearchEngine(SearchEngine):
    def search(self, query: str):
        # Simple implementation
        return [
            SearchResult(
                title="Example Result",
                url="https://example.com",
                snippet="This is an example search result",
                engine="mysearch"
            )
        ]
    
    @property
    def name(self):
        return "My Search"
    
    @property
    def icon(self):
        return "🔍"

# Register
search_manager.register_engine("mysearch", MySearchEngine())

Minimal AI Plugin

# my_ai_plugin.py
from naviduck.api import AIPlugin

class GreetingPlugin(AIPlugin):
    def can_handle(self, query: str):
        return query.lower() in ["hello", "hi", "hey"]
    
    def process(self, query: str):
        return "Hello! I'm your custom AI plugin."

# Register
navai.register_plugin(GreetingPlugin())

Package Your Extension

# Project structure
my-naviduck-extension/
├── plugin.json          # Manifest
├── __init__.py
├── search_engine.py     # Search engine implementation
├── ai_plugin.py         # AI plugin
└── README.md

# Install locally
pip install -e .

# Or package for distribution
python setup.py sdist bdist_wheel

📚 Best Practices

Extension Guidelines

  1. Keep Dependencies Minimal: Only require what's necessary
  2. Handle Errors Gracefully: Don't crash NaviDuck
  3. Respect User Privacy: Don't collect data without consent
  4. Follow Style Guidelines: Match NaviDuck's code style
  5. Provide Documentation: Document your extension
  6. Test Thoroughly: Ensure compatibility
  7. Version Your Extensions: Use semantic versioning

Performance Considerations

  • Cache expensive operations
  • Use async for I/O operations when possible
  • Clean up resources properly
  • Monitor memory usage

Security Considerations

  • Validate all input
  • Sanitize output
  • Use environment variables for secrets
  • Implement rate limiting
  • Follow least privilege principle

🆘 Support & Resources

Getting Help

  • Check existing extensions for examples
  • Review the source code
  • Ask in GitHub Discussions
  • Create an issue for bug reports

Testing Your Extension

# Test script
from my_extension import MySearchEngine

def test_extension():
    engine = MySearchEngine()
    results = engine.search("test")
    assert len(results) > 0
    print("✅ Extension works!")

if __name__ == "__main__":
    test_extension()

Debugging Tips

# Enable debug logging
import logging
logging.basicConfig(level=logging.DEBUG)

# Use the profiler
profiler.start("my_extension")
# ... your code ...
profiler.stop("my_extension")
profiler.print_report()

Last updated: 12/22/2025
API Reference version: 3.0

Ready to extend NaviDuck? Start with a simple plugin and grow from there. The community welcomes your contributions! 🚀

Clone this wiki locally