Refactor SEO automation into unified CLI application
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>
This commit is contained in:
110
scripts/config.py
Normal file
110
scripts/config.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""
|
||||
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()
|
||||
Reference in New Issue
Block a user