Major refactoring to create a clean, integrated CLI application: ### New Features: - Unified CLI executable (./seo) with simple command structure - All commands accept optional CSV file arguments - Auto-detection of latest files when no arguments provided - Simplified output directory structure (output/ instead of output/reports/) - Cleaner export filename format (all_posts_YYYY-MM-DD.csv) ### Commands: - export: Export all posts from WordPress sites - analyze [csv]: Analyze posts with AI (optional CSV input) - recategorize [csv]: Recategorize posts with AI - seo_check: Check SEO quality - categories: Manage categories across sites - approve [files]: Review and approve recommendations - full_pipeline: Run complete workflow - analytics, gaps, opportunities, report, status ### Changes: - Moved all scripts to scripts/ directory - Created config.yaml for configuration - Updated all scripts to use output/ directory - Deprecated old seo-cli.py in favor of new ./seo - Added AGENTS.md and CHANGELOG.md documentation - Consolidated README.md with updated usage ### Technical: - Added PyYAML dependency - Removed hardcoded configuration values - All scripts now properly integrated - Better error handling and user feedback Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
111 lines
5.3 KiB
Python
111 lines
5.3 KiB
Python
"""
|
|
Configuration module for WordPress SEO automation.
|
|
Loads and validates environment variables and YAML configuration.
|
|
"""
|
|
|
|
import os
|
|
import yaml
|
|
from dotenv import load_dotenv
|
|
from pathlib import Path
|
|
|
|
# Load environment variables from .env file
|
|
load_dotenv()
|
|
|
|
class Config:
|
|
"""Configuration class for WordPress SEO automation."""
|
|
|
|
# Load configuration from YAML file
|
|
CONFIG_FILE = Path(__file__).parent.parent / 'config.yaml'
|
|
|
|
if CONFIG_FILE.exists():
|
|
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
|
|
YAML_CONFIG = yaml.safe_load(f)
|
|
else:
|
|
YAML_CONFIG = {}
|
|
|
|
# WordPress Settings (Primary site)
|
|
WORDPRESS_URL = os.getenv('WORDPRESS_URL', YAML_CONFIG.get('primary_site', {}).get('url', '')).rstrip('/')
|
|
WORDPRESS_USERNAME = os.getenv('WORDPRESS_USERNAME', YAML_CONFIG.get('primary_site', {}).get('username', ''))
|
|
WORDPRESS_APP_PASSWORD = os.getenv('WORDPRESS_APP_PASSWORD', YAML_CONFIG.get('primary_site', {}).get('password', ''))
|
|
|
|
# Multi-site WordPress Configuration
|
|
WORDPRESS_SITES = {
|
|
'mistergeek.net': {
|
|
'url': os.getenv('WORDPRESS_MISTERGEEK_URL', YAML_CONFIG.get('wordpress_sites', {}).get('mistergeek.net', {}).get('url', 'https://www.mistergeek.net')),
|
|
'username': os.getenv('WORDPRESS_MISTERGEEK_USERNAME', os.getenv('WORDPRESS_USERNAME', YAML_CONFIG.get('wordpress_sites', {}).get('mistergeek.net', {}).get('username', ''))),
|
|
'password': os.getenv('WORDPRESS_MISTERGEEK_PASSWORD', os.getenv('WORDPRESS_APP_PASSWORD', YAML_CONFIG.get('wordpress_sites', {}).get('mistergeek.net', {}).get('password', ''))),
|
|
},
|
|
'webscroll.fr': {
|
|
'url': os.getenv('WORDPRESS_WEBSCROLL_URL', YAML_CONFIG.get('wordpress_sites', {}).get('webscroll.fr', {}).get('url', 'https://www.webscroll.fr')),
|
|
'username': os.getenv('WORDPRESS_WEBSCROLL_USERNAME', os.getenv('WORDPRESS_USERNAME', YAML_CONFIG.get('wordpress_sites', {}).get('webscroll.fr', {}).get('username', ''))),
|
|
'password': os.getenv('WORDPRESS_WEBSCROLL_PASSWORD', os.getenv('WORDPRESS_APP_PASSWORD', YAML_CONFIG.get('wordpress_sites', {}).get('webscroll.fr', {}).get('password', ''))),
|
|
},
|
|
'hellogeek.net': {
|
|
'url': os.getenv('WORDPRESS_HELLOGEEK_URL', YAML_CONFIG.get('wordpress_sites', {}).get('hellogeek.net', {}).get('url', 'https://www.hellogeek.net')),
|
|
'username': os.getenv('WORDPRESS_HELLOGEEK_USERNAME', os.getenv('WORDPRESS_USERNAME', YAML_CONFIG.get('wordpress_sites', {}).get('hellogeek.net', {}).get('username', ''))),
|
|
'password': os.getenv('WORDPRESS_HELLOGEEK_PASSWORD', os.getenv('WORDPRESS_APP_PASSWORD', YAML_CONFIG.get('wordpress_sites', {}).get('hellogeek.net', {}).get('password', ''))),
|
|
}
|
|
}
|
|
|
|
# OpenRouter API Settings
|
|
OPENROUTER_API_KEY = os.getenv('OPENROUTER_API_KEY', YAML_CONFIG.get('ai_model', {}).get('api_key', ''))
|
|
AI_MODEL = os.getenv('AI_MODEL', YAML_CONFIG.get('ai_model', {}).get('name', 'anthropic/claude-3.5-sonnet'))
|
|
|
|
# Script Settings
|
|
BATCH_SIZE = int(os.getenv('BATCH_SIZE', str(YAML_CONFIG.get('script_settings', {}).get('batch_size', 100))))
|
|
API_DELAY_SECONDS = float(os.getenv('API_DELAY_SECONDS', str(YAML_CONFIG.get('script_settings', {}).get('api_delay_seconds', 0.5))))
|
|
|
|
# Analysis Settings
|
|
ANALYSIS_MIN_POSITION = int(os.getenv('ANALYSIS_MIN_POSITION', str(YAML_CONFIG.get('analysis_settings', {}).get('min_position', 11))))
|
|
ANALYSIS_MAX_POSITION = int(os.getenv('ANALYSIS_MAX_POSITION', str(YAML_CONFIG.get('analysis_settings', {}).get('max_position', 30))))
|
|
ANALYSIS_MIN_IMPRESSIONS = int(os.getenv('ANALYSIS_MIN_IMPRESSIONS', str(YAML_CONFIG.get('analysis_settings', {}).get('min_impressions', 50))))
|
|
ANALYSIS_TOP_N_POSTS = int(os.getenv('ANALYSIS_TOP_N_POSTS', str(YAML_CONFIG.get('analysis_settings', {}).get('top_n_posts', 20))))
|
|
|
|
# Output directory
|
|
OUTPUT_DIR = Path(os.getenv('OUTPUT_DIR', YAML_CONFIG.get('output_settings', {}).get('output_dir', './output')))
|
|
|
|
@classmethod
|
|
def validate(cls):
|
|
"""Validate that all required configuration is present."""
|
|
errors = []
|
|
|
|
if not cls.WORDPRESS_URL:
|
|
errors.append("WORDPRESS_URL is required")
|
|
|
|
if not cls.WORDPRESS_USERNAME:
|
|
errors.append("WORDPRESS_USERNAME is required")
|
|
|
|
if not cls.WORDPRESS_APP_PASSWORD:
|
|
errors.append("WORDPRESS_APP_PASSWORD is required")
|
|
|
|
if not cls.OPENROUTER_API_KEY:
|
|
errors.append("OPENROUTER_API_KEY is required (get one from https://openrouter.ai/)")
|
|
|
|
if errors:
|
|
raise ValueError("Configuration errors:\n" + "\n".join(f" - {e}" for e in errors))
|
|
|
|
# Create output directory if it doesn't exist
|
|
cls.OUTPUT_DIR.mkdir(exist_ok=True)
|
|
|
|
return True
|
|
|
|
@classmethod
|
|
def get_wordpress_auth(cls):
|
|
"""Get WordPress authentication tuple."""
|
|
return (cls.WORDPRESS_USERNAME, cls.WORDPRESS_APP_PASSWORD)
|
|
|
|
@classmethod
|
|
def get_api_base_url(cls):
|
|
"""Get WordPress REST API base URL."""
|
|
return f"{cls.WORDPRESS_URL}/wp-json/wp/v2"
|
|
|
|
@classmethod
|
|
def get_site_config(cls, site_name):
|
|
"""Get configuration for a specific site."""
|
|
return cls.WORDPRESS_SITES.get(site_name, {})
|
|
|
|
@classmethod
|
|
def get_all_sites(cls):
|
|
"""Get all configured WordPress sites."""
|
|
return cls.WORDPRESS_SITES.keys()
|