-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
Dragon edited this page Dec 22, 2025
·
1 revision
Last updated: 12/22/2025
NaviDuck is built as a modular, extensible CLI browser with a clear separation of concerns. The architecture follows a layered design pattern with components communicating through well-defined interfaces.
graph TB
subgraph "User Interface Layer"
UI[UIManager]
CLI[Command Line Interface]
UX[User Experience]
end
subgraph "Business Logic Layer"
SM[Search Manager]
AI[NavAI Assistant]
PL[Page Loader]
NM[Network Manager]
end
subgraph "Data Layer"
BS[Browser State]
DS[Data Storage]
TM[Tor Manager]
end
subgraph "External Services"
SE[Search Engines]
WEB[Web Content]
TOR[Tor Network]
end
CLI --> UI
UI --> SM
UI --> AI
UI --> PL
UI --> BS
SM --> NM
AI --> NM
PL --> NM
NM --> SE
NM --> WEB
NM --> TM
TM --> TOR
BS --> DS
SM --> DS
AI --> DS
style UI fill:#e1f5fe
style SM fill:#f3e5f5
style AI fill:#e8f5e8
style BS fill:#fff3e0
style NM fill:#fce4ec
Each component is self-contained with clear interfaces:
- Loose Coupling: Components communicate through interfaces, not direct dependencies
- High Cohesion: Each component has a single, well-defined responsibility
- Replaceable: Components can be swapped without affecting others
┌─────────────────────────────────┐
│ User Interface │ ← Presentation Layer
├─────────────────────────────────┤
│ Business Logic │ ← Application Layer
├─────────────────────────────────┤
│ Data Access / Services │ ← Data Layer
├─────────────────────────────────┤
│ External APIs / Network │ ← Infrastructure Layer
└─────────────────────────────────┘
Components communicate through events and callbacks:
- Command Pattern: User actions as commands
- Observer Pattern: State changes notify interested components
- Strategy Pattern: Different algorithms for search, parsing, etc.
- Graceful Degradation: Features degrade gracefully when dependencies fail
- Fallback Systems: Multiple fallback mechanisms for critical functions
- Error Isolation: Failures in one component don't crash the system
- Manage application state and configuration
- Handle data persistence
- Coordinate between components
- Maintain session information
class BrowserState:
def __init__(self):
# Configuration
self.use_emoji: bool
self.icons: dict
self.current_engine: str
# Data Stores
self.history: List[dict]
self.bookmarks: List[dict]
self.current_results: List[dict]
# Session State
self.current_page: str
self.current_url: str
self.current_title: str
# Network
self.tor_enabled: bool
self.tor_process: Optional[Process]
# File Paths
self.data_file: str
self.config_file: str
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"""graph LR
A[User Action] --> B[BrowserState.update]
B --> C[State Change]
C --> D[Notify Components]
D --> E[UI Update]
D --> F[Data Persistence]
E --> G[User Feedback]
F --> H[Disk Storage]
- Handle all user interactions
- Render UI components
- Process user commands
- Manage input/output
Command Pattern Implementation:
class CommandHandler:
def __init__(self):
self.commands = {
's': self.handle_search,
'search': self.handle_search,
'ai': self.handle_ai,
'go': self.handle_navigation,
# ... more commands
}
def handle_command(self, cmd_line: str) -> bool:
parts = cmd_line.strip().split()
if not parts:
return False
cmd = parts[0].lower()
handler = self.commands.get(cmd)
if handler:
return handler(parts[1:])
else:
# Try as direct query
return self.handle_unknown(cmd_line)Observer Pattern for State Updates:
class UIManager:
def __init__(self, state: BrowserState):
self.state = state
self.state.add_observer(self) # Subscribe to state changes
def on_state_change(self, change_type: str, data: dict):
"""Called when BrowserState changes"""
if change_type == 'search_complete':
self.show_results(data['results'])
elif change_type == 'page_loaded':
self.show_page(data['page'])
# ... more change typesUIManager
├── InputHandler (Command parsing)
├── ScreenRenderer (Display management)
├── ComponentFactory (UI element creation)
└── EventDispatcher (User event routing)
- Query multiple search engines
- Parse and normalize results
- Handle CAPTCHA and rate limiting
- Implement fallback strategies
class SearchPipeline:
def search(self, query: str, engine: str = None) -> List[dict]:
# 1. Query Validation
validated = self.validate_query(query)
# 2. Engine Selection
engine = self.select_engine(engine, query)
# 3. Request Construction
request = self.build_request(query, engine)
# 4. Network Request
response = self.make_request(request)
# 5. CAPTCHA Detection
if self.detect_captcha(response):
return self.handle_captcha(query, engine)
# 6. Result Parsing
results = self.parse_results(response, engine)
# 7. Result Ranking
ranked = self.rank_results(results, query)
# 8. Caching
self.cache_results(query, engine, ranked)
return rankedclass SearchEngineStrategy:
def __init__(self):
self.engines = {
'ddg': DuckDuckGoStrategy(),
'ddg_api': DuckDuckGoAPIStrategy(),
'google': GoogleStrategy(),
'wikipedia': WikipediaStrategy(),
'brave': BraveStrategy(),
}
def search(self, query: str, engine: str) -> List[dict]:
strategy = self.engines.get(engine)
if not strategy:
strategy = self.get_fallback_strategy()
try:
return strategy.execute(query)
except SearchFailed:
return self.try_alternative(query, engine)graph TD
A[Start Search] --> B{Detect CAPTCHA}
B -->|Yes| C[Log CAPTCHA Event]
C --> D[Switch Engine]
D --> E[Retry Search]
E --> F{Success?}
F -->|No| G[Next Fallback]
F -->|Yes| H[Return Results]
B -->|No| I[Parse Results]
I --> H
G --> D
- Handle all HTTP/HTTPS requests
- Manage connection pooling
- Implement retry logic
- Handle proxies and Tor routing
class ConnectionPool:
def __init__(self, max_pool_size: int = 10):
self.pools = {}
self.max_pool_size = max_pool_size
def get_connection(self, host: str) -> Connection:
if host not in self.pools:
self.pools[host] = ConnectionPoolForHost(host, self.max_pool_size)
return self.pools[host].get_connection()
def release_connection(self, host: str, conn: Connection):
self.pools[host].release_connection(conn)class RequestPipeline:
def __init__(self):
self.middleware = [
UserAgentMiddleware(),
RetryMiddleware(max_retries=3),
TimeoutMiddleware(timeout=10),
GzipMiddleware(),
CookieMiddleware(),
CacheMiddleware(),
TorMiddleware(), # Conditional
]
def execute(self, request: Request) -> Response:
response = request
for middleware in self.middleware:
if middleware.should_process(request):
response = middleware.process(response)
return response- Process natural language queries
- Integrate with search APIs
- Maintain conversation context
- Provide intelligent responses
class AIPipeline:
def process_query(self, query: str) -> str:
# 1. Query Classification
query_type = self.classify_query(query)
# 2. Intent Recognition
intent = self.extract_intent(query)
# 3. Context Integration
context = self.get_context()
# 4. Knowledge Source Selection
sources = self.select_sources(query_type, intent)
# 5. Parallel Knowledge Gathering
knowledge = self.gather_knowledge(sources, query, context)
# 6. Response Generation
response = self.generate_response(knowledge, query_type)
# 7. Context Update
self.update_context(query, response)
return responseNavAI Knowledge System
├── Local Knowledge Base (Pre-defined responses)
├── DuckDuckGo Instant Answer API
├── Web Search Integration
├── Conversation Memory
└── Response Templates
- Manage Tor process lifecycle
- Handle SOCKS5 proxy configuration
- Monitor Tor circuit health
- Implement bridge support
class TorProcessManager:
def __init__(self):
self.process = None
self.data_dir = None
self.port = 9050
def start(self) -> bool:
# 1. Create temp directory
self.data_dir = tempfile.mkdtemp()
# 2. Build command
cmd = self.build_tor_command()
# 3. Start process
self.process = subprocess.Popen(cmd, ...)
# 4. Wait for bootstrap
return self.wait_for_bootstrap()
def build_tor_command(self) -> List[str]:
return [
self.tor_exe,
"--SocksPort", str(self.port),
"--DataDirectory", self.data_dir,
"--Log", "notice stdout",
"--AvoidDiskWrites", "1",
]class TorCircuitManager:
def __init__(self):
self.circuits = {}
self.current_circuit = None
def new_circuit(self) -> str:
"""Create new Tor circuit"""
circuit_id = self.generate_circuit_id()
self.circuits[circuit_id] = {
'created': time.time(),
'requests': 0,
'bytes_sent': 0,
'bytes_received': 0,
}
self.current_circuit = circuit_id
return circuit_id
def should_rotate(self, circuit_id: str) -> bool:
"""Determine if circuit should be rotated"""
circuit = self.circuits.get(circuit_id)
if not circuit:
return True
# Rotate based on age or usage
age = time.time() - circuit['created']
return age > 600 or circuit['requests'] > 100 # 10 min or 100 requestssequenceDiagram
participant U as User
participant UI as UIManager
participant BS as BrowserState
participant SM as SearchManager
participant NM as NetworkManager
participant SE as Search Engine
participant DS as Data Storage
U->>UI: Enter search query
UI->>BS: Validate query
BS->>UI: Return validation result
UI->>SM: Execute search
SM->>NM: Make HTTP request
NM->>SE: Send search request
SE->>NM: Return HTML/JSON
NM->>SM: Pass response
SM->>SM: Parse results
SM->>BS: Store results
BS->>DS: Persist to disk
BS->>UI: Notify completion
UI->>U: Display results
sequenceDiagram
participant U as User
participant UI as UIManager
participant AI as NavAI
participant KB as Knowledge Base
participant DDG as DuckDuckGo API
participant SM as SearchManager
U->>UI: Ask AI question
UI->>AI: Process query
AI->>KB: Check local knowledge
KB->>AI: Return local answer
alt Local answer found
AI->>UI: Return local answer
else No local answer
AI->>DDG: Query Instant Answer API
DDG->>AI: Return API answer
alt API answer valid
AI->>UI: Return API answer
else No API answer
AI->>SM: Fallback to search
SM->>AI: Return search results
AI->>UI: Return search-based answer
end
end
UI->>U: Display answer
sequenceDiagram
participant U as User
participant UI as UIManager
participant PL as PageLoader
participant NM as NetworkManager
participant TM as TorManager
participant WEB as Website
participant BS as BrowserState
U->>UI: Request URL
UI->>PL: Load page
PL->>BS: Check Tor requirement
alt .onion or Tor enabled
BS->>TM: Get Tor proxy
TM->>PL: Return proxy config
PL->>NM: Request via Tor
else Regular site
PL->>NM: Direct request
end
NM->>WEB: HTTP GET
WEB->>NM: Return content
NM->>PL: Pass response
PL->>PL: Extract content
PL->>BS: Store page data
BS->>UI: Notify completion
UI->>U: Display page
{
"version": "1.0",
"default_engine": "brave",
"use_emoji": false,
"tor_enabled": false,
"user_agent": "Mozilla/5.0...",
"engines": {
"ddg": true,
"ddg_api": true,
"google": false,
"wikipedia": true,
"brave": true
},
"privacy": {
"clear_history_on_exit": false,
"strip_tracking_params": true,
"randomize_user_agent": false
},
"performance": {
"cache_enabled": true,
"cache_ttl": 300,
"timeout": 10
}
}{
"history": [
{
"id": "timestamp_hash",
"type": "search|visit|ai",
"timestamp": "2024-01-15T10:30:00",
"data": {
"query": "python programming",
"engine": "brave",
"results_count": 10
}
}
],
"bookmarks": [
{
"id": "url_hash",
"title": "Python Official Website",
"url": "https://python.org",
"added": "2024-01-15T10:30:00",
"tags": ["programming", "python"],
"notes": "Main Python website"
}
],
"sessions": {
"last_session": {
"timestamp": "2024-01-15T10:30:00",
"queries": ["python", "flask"],
"visited": ["https://python.org"]
}
}
}~/.naviduck_cache/
├── search/
│ ├── brave/
│ │ ├── python_123abc.cache
│ │ └── flask_456def.cache
│ └── ddg_api/
│ └── ai_query_789ghi.cache
├── pages/
│ └── https_python.org_index.html.cache
└── ai/
└── responses/
└── what_is_python.cache
class DataRepository:
def __init__(self, storage_backend):
self.storage = storage_backend
def save_history(self, entry: dict) -> bool:
"""Save history entry with validation"""
validated = self.validate_history_entry(entry)
return self.storage.append('history', validated)
def get_recent_history(self, limit: int = 50) -> List[dict]:
"""Get recent history entries"""
return self.storage.get_slice('history', -limit)
def clear_old_history(self, max_age_days: int) -> int:
"""Clear history older than specified days"""
cutoff = datetime.now() - timedelta(days=max_age_days)
return self.storage.delete_by_condition(
'history',
lambda x: datetime.fromisoformat(x['timestamp']) < cutoff
)class MultiLevelCache:
def __init__(self):
self.memory_cache = {} # LRU in-memory cache
self.disk_cache = DiskCache() # Persistent disk cache
def get(self, key: str):
# 1. Try memory cache
if key in self.memory_cache:
entry = self.memory_cache[key]
if not self.is_expired(entry):
self.update_lru(key)
return entry['data']
# 2. Try disk cache
disk_data = self.disk_cache.get(key)
if disk_data and not self.is_expired(disk_data):
# Promote to memory cache
self.memory_cache[key] = disk_data
return disk_data['data']
# 3. Cache miss
return None
def set(self, key: str, data, ttl: int = 300):
entry = {
'data': data,
'timestamp': time.time(),
'ttl': ttl
}
# Update both caches
self.memory_cache[key] = entry
self.disk_cache.set(key, entry)
# Enforce memory cache limits
if len(self.memory_cache) > self.max_memory_entries:
self.evict_oldest()class NaviDuckPlugin:
"""Base class for all plugins"""
def __init__(self, context):
self.context = context # Access to NaviDuck APIs
self.name = "Unnamed Plugin"
self.version = "1.0"
self.description = ""
def on_load(self) -> bool:
"""Called when plugin is loaded"""
return True
def on_unload(self) -> None:
"""Called when plugin is unloaded"""
pass
def on_command(self, command: str, args: List[str]) -> Optional[bool]:
"""Handle custom commands"""
return None # Return True if handled
def on_search(self, query: str, engine: str, results: List[dict]) -> List[dict]:
"""Modify search results"""
return results
def on_page_load(self, url: str, content: str) -> str:
"""Modify page content"""
return content
def register_commands(self) -> Dict[str, Callable]:
"""Register custom commands"""
return {}class DarkReaderPlugin(NaviDuckPlugin):
def __init__(self, context):
super().__init__(context)
self.name = "Dark Reader"
self.version = "1.0"
self.description = "Dark mode for web pages"
def on_page_load(self, url: str, content: str) -> str:
"""Apply dark theme to HTML"""
if not self.is_html(content):
return content
# Simple dark theme CSS injection
dark_css = """
<style>
body { background: #1a1a1a; color: #e0e0e0; }
a { color: #80c0ff; }
</style>
"""
# Inject CSS into HTML
return self.inject_css(content, dark_css)class SearchEnginePlugin(NaviDuckPlugin):
def get_engine_config(self) -> dict:
return {
"id": "mysearch",
"name": "My Search Engine",
"url": "https://api.example.com/search",
"parser": self.parse_results,
"icon": "SEARCH"
}class ContentFilterPlugin(NaviDuckPlugin):
def __init__(self, context):
super().__init__(context)
self.filters = [
self.remove_ads,
self.clean_tracking,
self.simplify_layout
]
def on_page_load(self, url: str, content: str) -> str:
for filter_func in self.filters:
content = filter_func(content)
return contentclass UIThemePlugin(NaviDuckPlugin):
def get_theme(self) -> dict:
return {
"colors": {
"PROMPT": "\033[1;35m",
"SUCCESS": "\033[1;32m",
"ERROR": "\033[1;31m"
},
"icons": {
"SEARCH": "🔎",
"AI": "🤖",
"BOOKMARK": "📑"
}
}class SecurityValidator:
def validate_url(self, url: str) -> bool:
# Parse and validate URL
parsed = urlparse(url)
# Block dangerous schemes
if parsed.scheme not in ['http', 'https']:
return False
# Block localhost/internal addresses
if self.is_local_address(parsed.netloc):
return False
# Block known malicious patterns
if self.contains_malicious_patterns(url):
return False
return True
def sanitize_query(self, query: str) -> str:
"""Sanitize search queries"""
# Remove control characters
sanitized = ''.join(char for char in query if ord(char) >= 32)
# Limit length
if len(sanitized) > 500:
sanitized = sanitized[:500]
return sanitizedclass NetworkSecurity:
def __init__(self):
self.ssl_context = self.create_secure_ssl_context()
self.request_validator = RequestValidator()
def create_secure_ssl_context(self) -> ssl.SSLContext:
context = ssl.create_default_context()
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20')
return context
def make_secure_request(self, url: str) -> Response:
if not self.request_validator.is_allowed(url):
raise SecurityError("URL not allowed")
# Use secure SSL context
response = requests.get(
url,
timeout=10,
verify=self.ssl_context
)
# Validate response
self.validate_response(response)
return responseclass DataProtection:
def __init__(self, encryption_key: bytes):
self.cipher = Fernet(encryption_key)
def encrypt_data(self, data: dict) -> bytes:
"""Encrypt sensitive data"""
json_data = json.dumps(data).encode()
return self.cipher.encrypt(json_data)
def decrypt_data(self, encrypted: bytes) -> dict:
"""Decrypt sensitive data"""
decrypted = self.cipher.decrypt(encrypted)
return json.loads(decrypted)
def secure_delete(self, filepath: str) -> None:
"""Securely delete file by overwriting"""
with open(filepath, 'rb+') as f:
length = f.tell()
f.seek(0)
f.write(os.urandom(length))
os.remove(filepath)| Threat | Layer | Mitigation |
|---|---|---|
| SQL Injection | Input Validation | Parameter sanitization |
| XSS | Output Encoding | HTML entity encoding |
| CSRF | Session Management | Token validation |
| MITM | Network Security | SSL/TLS, certificate pinning |
| Data Theft | Data Protection | Encryption at rest |
| DoS | Rate Limiting | Request throttling |
| Fingerprinting | Privacy Layer | Randomization, Tor |
┌─────────────────────────────────┐
│ Memory Cache (LRU) │ ← Fastest, 1000 entries
├─────────────────────────────────┤
│ Disk Cache (SSD/HDD) │ ← Persistent, 10k entries
├─────────────────────────────────┤
│ CDN/Edge Cache │ ← External, distributed
├─────────────────────────────────┤
│ Origin Server │ ← Slowest, always fresh
└─────────────────────────────────┘
class PerformanceOptimizer:
def __init__(self):
self.caches = {
'search': LRUCache(maxsize=100),
'pages': LRUCache(maxsize=50),
'ai': LRUCache(maxsize=200),
}
self.metrics = {
'cache_hits': 0,
'cache_misses': 0,
'avg_response_time': 0,
}
def cached_search(self, query: str, engine: str) -> List[dict]:
cache_key = f"{engine}:{query}"
# Check cache
cached = self.caches['search'].get(cache_key)
if cached and not self.is_stale(cached):
self.metrics['cache_hits'] += 1
return cached
# Cache miss - perform actual search
self.metrics['cache_misses'] += 1
start_time = time.time()
results = self.perform_search(query, engine)
# Calculate response time
response_time = time.time() - start_time
self.update_avg_time(response_time)
# Cache results
self.caches['search'].set(cache_key, results, ttl=300)
return resultsclass ConnectionPoolManager:
def __init__(self, max_pool_size: int = 10):
self.pools = {}
self.max_pool_size = max_pool_size
self.stats = defaultdict(int)
def get_connection(self, host: str) -> Connection:
if host not in self.pools:
self.pools[host] = ConnectionPool(
host=host,
max_size=self.max_pool_size
)
conn = self.pools[host].get_connection()
self.stats['connections_used'] += 1
return conn
def release_connection(self, host: str, conn: Connection):
self.pools[host].release_connection(conn)
self.stats['connections_released'] += 1class LazyLoader:
def __init__(self, loader_func):
self.loader_func = loader_func
self._value = None
self._loaded = False
@property
def value(self):
if not self._loaded:
self._value = self.loader_func()
self._loaded = True
return self._value
def invalidate(self):
self._loaded = False
self._value = Noneclass NaviDuckError(Exception):
"""Base exception for all NaviDuck errors"""
pass
class NetworkError(NaviDuckError):
"""Network-related errors"""
pass
class SearchError(NaviDuckError):
"""Search-related errors"""
pass
class AIError(NaviDuckError):
"""AI-related errors"""
pass
class ConfigurationError(NaviDuckError):
"""Configuration-related errors"""
pass
class SecurityError(NaviDuckError):
"""Security-related errors"""
passclass ErrorRecovery:
def __init__(self):
self.recovery_strategies = {
NetworkError: self.recover_from_network_error,
SearchError: self.recover_from_search_error,
AIError: self.recover_from_ai_error,
}
def handle_error(self, error: Exception, context: dict) -> Any:
"""Handle error with appropriate recovery strategy"""
error_type = type(error)
if error_type in self.recovery_strategies:
return self.recovery_strategies[error_type](error, context)
# Default recovery
return self.default_recovery(error, context)
def recover_from_network_error(self, error: NetworkError, context: dict):
# 1. Retry with exponential backoff
for attempt in range(3):
try:
return self.retry_operation(context)
except:
time.sleep(2 ** attempt) # Exponential backoff
# 2. Switch to alternative endpoint
return self.use_alternative_endpoint(context)
def recover_from_search_error(self, error: SearchError, context: dict):
# 1. Switch search engine
alternative_engine = self.get_alternative_engine()
return self.retry_with_engine(context, alternative_engine)class CircuitBreaker:
def __init__(self, failure_threshold: int = 5, reset_timeout: int = 60):
self.failure_threshold = failure_threshold
self.reset_timeout = reset_timeout
self.failures = 0
self.last_failure = None
self.state = "CLOSED" # CLOSED, OPEN, HALF_OPEN
def execute(self, operation: Callable) -> Any:
if self.state == "OPEN":
if self.should_try_reset():
self.state = "HALF_OPEN"
else:
raise CircuitBreakerOpen("Circuit breaker is open")
try:
result = operation()
self.on_success()
return result
except Exception as e:
self.on_failure()
raise
def on_failure(self):
self.failures += 1
self.last_failure = time.time()
if self.failures >= self.failure_threshold:
self.state = "OPEN"
def on_success(self):
self.failures = 0
self.state = "CLOSED" ┌─────────────────────┐
│ E2E Tests │ ← 10% of tests
│ (Full system) │
├─────────────────────┤
│ Integration Tests │ ← 20% of tests
│ (Component interaction)│
├─────────────────────┤
│ Unit Tests │ ← 70% of tests
│ (Individual units) │
└─────────────────────┘
# tests/
# ├── unit/
# │ ├── test_browser_state.py
# │ ├── test_search_manager.py
# │ ├── test_network_manager.py
# │ └── test_ui_manager.py
# ├── integration/
# │ ├── test_search_flow.py
# │ ├── test_ai_flow.py
# │ └── test_tor_integration.py
# ├── e2e/
# │ ├── test_full_search.py
# │ └── test_user_journey.py
# └── conftest.pyclass MockNetworkManager(NetworkManager):
def __init__(self):
self.responses = {}
self.requests = []
def add_mock_response(self, url: str, response: dict):
self.responses[url] = response
def get(self, url: str, **kwargs) -> Response:
self.requests.append({
'url': url,
'timestamp': time.time(),
'kwargs': kwargs
})
if url in self.responses:
return MockResponse(self.responses[url])
# Default mock response
return MockResponse({
'status_code': 200,
'text': '<html>Mock response</html>',
'headers': {'Content-Type': 'text/html'}
})# .github/workflows/build.yml
name: Build and Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [3.8, 3.9, 3.10, 3.11]
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest pytest-cov
- name: Run tests
run: |
pytest --cov=naviduck tests/
- name: Upload coverage
uses: codecov/codecov-action@v2# setup.py
from setuptools import setup, find_packages
setup(
name="naviduck",
version="1.0.0",
packages=find_packages(),
install_requires=[
"requests>=2.25.0",
],
extras_require={
'dev': [
'pytest>=6.0',
'pytest-cov>=2.0',
'black>=21.0',
'flake8>=3.9',
],
'tor': [
'stem>=1.8.0',
],
},
entry_points={
'console_scripts': [
'naviduck=naviduck.main:main',
],
},
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
)class MetricsCollector:
def __init__(self):
self.metrics = {
'search': {
'count': 0,
'success': 0,
'failure': 0,
'avg_time': 0,
},
'ai': {
'queries': 0,
'api_calls': 0,
'cache_hits': 0,
},
'network': {
'requests': 0,
'bytes_sent': 0,
'bytes_received': 0,
}
}
def record_search(self, success: bool, duration: float):
self.metrics['search']['count'] += 1
if success:
self.metrics['search']['success'] += 1
else:
self.metrics['search']['failure'] += 1
# Update average (moving average)
current_avg = self.metrics['search']['avg_time']
n = self.metrics['search']['count']
self.metrics['search']['avg_time'] = (
current_avg * (n-1) + duration
) / n
def get_report(self) -> dict:
return {
'timestamp': time.time(),
'metrics': self.metrics,
'summary': self.generate_summary()
}class StructuredLogger:
def __init__(self):
self.loggers = {}
def get_logger(self, name: str):
if name not in self.loggers:
self.loggers[name] = Logger(name)
return self.loggers[name]
def log_event(self, event_type: str, data: dict):
log_entry = {
'timestamp': time.time(),
'event': event_type,
'data': data,
'context': self.get_context()
}
# Write to structured log file
self.write_log_entry(log_entry)
# Also output to console in dev mode
if self.is_dev_mode():
print(f"[{event_type}] {json.dumps(data)}")┌─────────────────────────────────────────────┐
│ API Gateway │
├─────────────────────────────────────────────┤
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Search │ │ AI │ │ Proxy │ │
│ │ Service │ │ Service │ │ Service │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────┘
- Dynamic plugin loading
- Plugin marketplace
- Sandboxed plugin execution
- Versioned plugin API
- Peer-to-peer search indexing
- Federated AI model training
- Distributed caching
- Edge computing support
# Current (v1.x)
Architecture: Monolithic Python CLI
Storage: Local JSON files
Networking: Direct HTTP requests
# Planned (v2.x)
Architecture: Microservices + CLI
Storage: SQLite + Redis cache
Networking: Async HTTP/WebSockets
# Future (v3.x)
Architecture: Distributed P2P
Storage: Distributed database
Networking: Libp2p + WebRTC- Separation of Concerns: Each component has a single responsibility
- Loose Coupling: Components communicate through interfaces
- High Cohesion: Related functionality grouped together
- Open/Closed: Open for extension, closed for modification
- Dependency Inversion: Depend on abstractions, not concretions
- Interface Segregation: Many specific interfaces vs one general
- Single Responsibility: Each class has one reason to change
- Performance: Caching, lazy loading, connection pooling
- Scalability: Stateless components, horizontal scaling
- Reliability: Error recovery, circuit breakers, retry logic
- Security: Defense in depth, input validation, encryption
- Maintainability: Clear interfaces, comprehensive tests, documentation
- Extensibility: Plugin architecture, configuration system
- Usability: Intuitive CLI, helpful errors, progressive disclosure
| Decision | Benefit | Trade-off |
|---|---|---|
| Monolithic CLI | Simple deployment | Harder to scale |
| JSON storage | Human readable | Not optimized for queries |
| Synchronous I/O | Simpler code | Lower concurrency |
| Regex parsing | No dependencies | Less robust than HTML parsers |
Last updated: 12/22/2025
Architecture version: 2.0
Key Takeaways:
- NaviDuck follows a layered, modular architecture
- Clear separation between UI, business logic, and data layers
- Designed for extensibility and maintainability
- Built with privacy and security as first-class concerns
- Architecture supports future evolution to distributed systems
The architecture balances simplicity with sophistication, providing a solid foundation for both current features and future expansion.