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:
388
scripts/seo-cli.py
Executable file
388
scripts/seo-cli.py
Executable file
@@ -0,0 +1,388 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
DEPRECATED: SEO Automation CLI
|
||||
|
||||
This script is deprecated. Please use the new unified CLI:
|
||||
- ./seo export
|
||||
- ./seo analyze
|
||||
- ./seo seo_check
|
||||
- ./seo categories
|
||||
- ./seo full_pipeline
|
||||
|
||||
To see all commands: ./seo help
|
||||
"""
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from config import Config
|
||||
import os
|
||||
|
||||
class SEOCLI:
|
||||
"""DEPRECATED: Main CLI orchestrator for SEO workflows. Use new ./seo CLI instead."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize CLI."""
|
||||
print("⚠️ DEPRECATION WARNING: This CLI is deprecated. Use ./seo instead.")
|
||||
print(" Run './seo help' to see new commands.")
|
||||
self.scripts_dir = Path(__file__).parent
|
||||
self.project_dir = self.scripts_dir.parent
|
||||
self.output_dir = self.project_dir / 'output' / 'reports'
|
||||
|
||||
def run_command(self, command, description):
|
||||
"""Run a command and show progress."""
|
||||
print(f"\n{'='*70}")
|
||||
print(f"▶ {description}")
|
||||
print(f"{'='*70}\n")
|
||||
|
||||
try:
|
||||
result = subprocess.run(command, shell=True, cwd=self.project_dir)
|
||||
if result.returncode != 0:
|
||||
print(f"\n❌ Error running: {description}")
|
||||
return False
|
||||
print(f"\n✓ {description} completed successfully")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error: {e}")
|
||||
return False
|
||||
|
||||
def get_latest_file(self, pattern):
|
||||
"""Get most recent file matching pattern."""
|
||||
import glob
|
||||
# Support both old and new naming patterns
|
||||
files = glob.glob(str(self.output_dir / pattern))
|
||||
if not files:
|
||||
# Try new pattern
|
||||
files = glob.glob(str(self.output_dir / "all_posts_*.csv"))
|
||||
if not files:
|
||||
return None
|
||||
return max(files, key=os.path.getctime)
|
||||
|
||||
def export_posts(self):
|
||||
"""Export all posts to CSV."""
|
||||
cmd = f"python {self.scripts_dir}/export_posts_for_ai_decision.py"
|
||||
return self.run_command(cmd, "STEP 1: Export All Posts")
|
||||
|
||||
def analyze_with_ai(self, csv_file=None):
|
||||
"""Analyze exported posts with AI."""
|
||||
if not csv_file:
|
||||
csv_file = self.get_latest_file("all_posts_for_ai_decision_*.csv")
|
||||
|
||||
if not csv_file:
|
||||
print("\n❌ No exported CSV found. Run 'seo-cli export' first.")
|
||||
return False
|
||||
|
||||
cmd = f"python {self.scripts_dir}/ai_analyze_posts_for_decisions.py \"{csv_file}\""
|
||||
return self.run_command(cmd, "STEP 2: Analyze with AI")
|
||||
|
||||
def recategorize_with_ai(self, csv_file=None):
|
||||
"""Recategorize posts using AI."""
|
||||
if not csv_file:
|
||||
csv_file = self.get_latest_file("all_posts_for_ai_decision_*.csv")
|
||||
|
||||
if not csv_file:
|
||||
print("\n❌ No exported CSV found. Run 'seo-cli export' first.")
|
||||
return False
|
||||
|
||||
cmd = f"python {self.scripts_dir}/ai_recategorize_posts.py \"{csv_file}\""
|
||||
return self.run_command(cmd, "Recategorizing Posts with AI")
|
||||
|
||||
def seo_check(self, top_n=None):
|
||||
"""Check SEO quality of titles and meta descriptions."""
|
||||
cmd = f"python {self.scripts_dir}/multi_site_seo_analyzer.py"
|
||||
if top_n:
|
||||
cmd += f" --top-n {top_n}"
|
||||
|
||||
return self.run_command(cmd, f"SEO Quality Check (Top {top_n or 'All'} posts)")
|
||||
|
||||
def import_analytics(self, ga_export, gsc_export, posts_csv=None):
|
||||
"""Import analytics data."""
|
||||
if not posts_csv:
|
||||
posts_csv = self.get_latest_file("all_posts_for_ai_decision_*.csv")
|
||||
|
||||
if not posts_csv:
|
||||
print("\n❌ No posts CSV found. Run 'seo-cli export' first.")
|
||||
return False
|
||||
|
||||
cmd = (
|
||||
f"python {self.scripts_dir}/analytics_importer.py "
|
||||
f"--ga-export \"{ga_export}\" "
|
||||
f"--gsc-export \"{gsc_export}\" "
|
||||
f"--posts-csv \"{posts_csv}\" "
|
||||
f"--output output/posts_with_analytics.csv"
|
||||
)
|
||||
return self.run_command(cmd, "STEP: Import Analytics Data")
|
||||
|
||||
def full_pipeline(self, analyze=True, seo=True):
|
||||
"""Run complete pipeline: export → analyze → seo check."""
|
||||
steps = [
|
||||
("Export", self.export_posts),
|
||||
]
|
||||
|
||||
if analyze:
|
||||
steps.append(("Analyze", self.analyze_with_ai))
|
||||
|
||||
if seo:
|
||||
steps.append(("SEO Check", self.seo_check))
|
||||
|
||||
print("\n" + "="*70)
|
||||
print("🚀 STARTING FULL PIPELINE")
|
||||
print("="*70)
|
||||
print(f"\nSteps to run: {', '.join([s[0] for s in steps])}\n")
|
||||
|
||||
completed = 0
|
||||
for name, func in steps:
|
||||
if func():
|
||||
completed += 1
|
||||
else:
|
||||
print(f"\n⚠️ Pipeline stopped at: {name}")
|
||||
return False
|
||||
|
||||
print("\n" + "="*70)
|
||||
print(f"✓ PIPELINE COMPLETE - All {completed} steps succeeded!")
|
||||
print("="*70)
|
||||
print("\nNext steps:")
|
||||
print("1. Review results in output/reports/")
|
||||
print("2. Check: posts_with_ai_recommendations_*.csv")
|
||||
print("3. Follow AI recommendations to optimize your content")
|
||||
return True
|
||||
|
||||
def manage_categories(self):
|
||||
"""Run category management with AI recommendations."""
|
||||
cmd = f"python {self.scripts_dir}/category_manager.py"
|
||||
return self.run_command(cmd, "Category Management with AI Recommendations")
|
||||
|
||||
def approve_recommendations(self, csv_files=None):
|
||||
"""Approve recommendations from CSV files."""
|
||||
if not csv_files:
|
||||
print("\n❌ No CSV files provided for approval.")
|
||||
return False
|
||||
|
||||
# Join the CSV files into a single command argument
|
||||
csv_files_str = " ".join(f'"{csv_file}"' for csv_file in csv_files)
|
||||
cmd = f"python {self.scripts_dir}/user_approval.py {csv_files_str}"
|
||||
return self.run_command(cmd, f"Approving Recommendations from {len(csv_files)} files")
|
||||
|
||||
def show_status(self):
|
||||
"""Show status of output files."""
|
||||
print("\n" + "="*70)
|
||||
print("📊 OUTPUT FILES STATUS")
|
||||
print("="*70 + "\n")
|
||||
|
||||
import glob
|
||||
files = glob.glob(str(self.output_dir / "*"))
|
||||
|
||||
if not files:
|
||||
print("No output files yet. Run 'seo-cli export' to get started.\n")
|
||||
return
|
||||
|
||||
# Sort by date
|
||||
files.sort(key=os.path.getctime, reverse=True)
|
||||
|
||||
for file in files[:10]: # Show last 10 files
|
||||
size = os.path.getsize(file) / 1024 # KB
|
||||
mtime = os.path.getmtime(file)
|
||||
from datetime import datetime
|
||||
date = datetime.fromtimestamp(mtime).strftime('%Y-%m-%d %H:%M:%S')
|
||||
filename = os.path.basename(file)
|
||||
|
||||
print(f" {filename}")
|
||||
print(f" Size: {size:.1f} KB | Modified: {date}")
|
||||
print()
|
||||
|
||||
def list_workflows(self):
|
||||
"""List available workflows."""
|
||||
workflows = {
|
||||
'export': {
|
||||
'description': 'Export all posts from your 3 WordPress sites',
|
||||
'command': 'seo-cli export',
|
||||
'time': '5-10 min',
|
||||
'cost': 'Free'
|
||||
},
|
||||
'analyze': {
|
||||
'description': 'Analyze exported posts with Claude AI',
|
||||
'command': 'seo-cli analyze',
|
||||
'time': '5-15 min',
|
||||
'cost': '$1.50-2.00'
|
||||
},
|
||||
'recategorize': {
|
||||
'description': 'Re-categorize posts for better organization',
|
||||
'command': 'seo-cli recategorize',
|
||||
'time': '5-15 min',
|
||||
'cost': '$1.50-2.00'
|
||||
},
|
||||
'seo-check': {
|
||||
'description': 'Check SEO quality of titles and descriptions',
|
||||
'command': 'seo-cli seo-check [--top-n 50]',
|
||||
'time': '3-5 min',
|
||||
'cost': 'Free or $0.20-0.50'
|
||||
},
|
||||
'analytics': {
|
||||
'description': 'Combine Google Analytics & Search Console data',
|
||||
'command': 'seo-cli analytics GA4.csv GSC.csv',
|
||||
'time': '5 min',
|
||||
'cost': 'Free'
|
||||
},
|
||||
'full-pipeline': {
|
||||
'description': 'Run complete pipeline: export → analyze → seo-check',
|
||||
'command': 'seo-cli full-pipeline',
|
||||
'time': '15-30 min',
|
||||
'cost': '$1.50-2.50'
|
||||
},
|
||||
'categories': {
|
||||
'description': 'Manage categories across all sites with AI recommendations',
|
||||
'command': 'seo-cli categories',
|
||||
'time': '10-20 min',
|
||||
'cost': '$0.50-1.00'
|
||||
},
|
||||
'approve': {
|
||||
'description': 'Review and approve SEO recommendations',
|
||||
'command': 'seo-cli approve [csv_file1] [csv_file2]',
|
||||
'time': 'Variable',
|
||||
'cost': 'Free'
|
||||
}
|
||||
}
|
||||
|
||||
print("\n" + "="*70)
|
||||
print("📋 AVAILABLE WORKFLOWS")
|
||||
print("="*70 + "\n")
|
||||
|
||||
for name, info in workflows.items():
|
||||
print(f"🔹 {name.upper()}")
|
||||
print(f" {info['description']}")
|
||||
print(f" Command: {info['command']}")
|
||||
print(f" Time: {info['time']} | Cost: {info['cost']}")
|
||||
print()
|
||||
|
||||
def show_help(self):
|
||||
"""Show help message."""
|
||||
print("\n" + "="*70)
|
||||
print("🚀 SEO AUTOMATION CLI - Workflow Orchestrator")
|
||||
print("="*70 + "\n")
|
||||
|
||||
print("QUICK START:")
|
||||
print(" seo-cli full-pipeline Run complete workflow")
|
||||
print(" seo-cli export Export all posts")
|
||||
print(" seo-cli analyze Analyze with AI")
|
||||
print(" seo-cli recategorize Re-categorize posts with AI")
|
||||
print(" seo-cli seo-check Check SEO quality")
|
||||
print()
|
||||
|
||||
print("CHAINING WORKFLOWS:")
|
||||
print(" seo-cli export && seo-cli analyze && seo-cli seo-check")
|
||||
print()
|
||||
|
||||
print("ADVANCED:")
|
||||
print(" seo-cli seo-check --top-n 50 Check top 50 posts")
|
||||
print(" seo-cli analytics GA4.csv GSC.csv Import analytics data")
|
||||
print(" seo-cli status Show output files")
|
||||
print(" seo-cli list List all workflows")
|
||||
print()
|
||||
|
||||
print("Learn more:")
|
||||
print(" Read: WORKFLOWS.md (complete guide)")
|
||||
print(" Read: scripts/*/README.md (workflow details)")
|
||||
print()
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
cli = SEOCLI()
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='SEO Automation CLI - Chain workflows together',
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
seo-cli export # Export posts
|
||||
seo-cli full-pipeline # Export + Analyze + SEO check
|
||||
seo-cli export && seo-cli analyze # Chain commands
|
||||
seo-cli seo-check --top-n 50 # Check top 50 posts
|
||||
seo-cli analytics ga4.csv gsc.csv # Import analytics
|
||||
seo-cli status # Show output files
|
||||
"""
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(dest='command', help='Workflow to run')
|
||||
|
||||
# Export workflow
|
||||
subparsers.add_parser('export', help='Export all posts from WordPress sites')
|
||||
|
||||
# Analyze workflow
|
||||
subparsers.add_parser('analyze', help='Analyze exported posts with Claude AI')
|
||||
|
||||
# Recategorize workflow
|
||||
subparsers.add_parser('recategorize', help='Re-categorize posts with Claude AI')
|
||||
|
||||
# SEO check workflow
|
||||
seo_parser = subparsers.add_parser('seo-check', help='Check SEO quality of titles/descriptions')
|
||||
seo_parser.add_argument('--top-n', type=int, help='Analyze top N posts with AI (costs money)')
|
||||
|
||||
# Analytics workflow
|
||||
analytics_parser = subparsers.add_parser('analytics', help='Import Google Analytics & Search Console')
|
||||
analytics_parser.add_argument('ga_export', help='Path to GA4 export CSV')
|
||||
analytics_parser.add_argument('gsc_export', help='Path to Search Console export CSV')
|
||||
|
||||
# Full pipeline
|
||||
full_parser = subparsers.add_parser('full-pipeline', help='Complete pipeline: export → analyze → seo-check')
|
||||
full_parser.add_argument('--no-analyze', action='store_true', help='Skip AI analysis')
|
||||
full_parser.add_argument('--no-seo', action='store_true', help='Skip SEO check')
|
||||
|
||||
# Category management
|
||||
subparsers.add_parser('categories', help='Manage categories with AI recommendations')
|
||||
|
||||
# Approval system
|
||||
approval_parser = subparsers.add_parser('approve', help='Approve recommendations from CSV files')
|
||||
approval_parser.add_argument('csv_files', nargs='*', help='CSV files containing recommendations to approve')
|
||||
|
||||
# Utilities
|
||||
subparsers.add_parser('status', help='Show status of output files')
|
||||
subparsers.add_parser('list', help='List all available workflows')
|
||||
subparsers.add_parser('help', help='Show this help message')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# If no command, show help
|
||||
if not args.command:
|
||||
cli.show_help()
|
||||
return 0
|
||||
|
||||
# Route to appropriate command
|
||||
if args.command == 'export':
|
||||
success = cli.export_posts()
|
||||
elif args.command == 'analyze':
|
||||
success = cli.analyze_with_ai()
|
||||
elif args.command == 'recategorize':
|
||||
success = cli.recategorize_with_ai()
|
||||
elif args.command == 'seo-check':
|
||||
success = cli.seo_check(top_n=args.top_n)
|
||||
elif args.command == 'analytics':
|
||||
success = cli.import_analytics(args.ga_export, args.gsc_export)
|
||||
elif args.command == 'full-pipeline':
|
||||
success = cli.full_pipeline(
|
||||
analyze=not args.no_analyze,
|
||||
seo=not args.no_seo
|
||||
)
|
||||
elif args.command == 'categories':
|
||||
success = cli.manage_categories()
|
||||
elif args.command == 'approve':
|
||||
success = cli.approve_recommendations(args.csv_files)
|
||||
elif args.command == 'status':
|
||||
cli.show_status()
|
||||
success = True
|
||||
elif args.command == 'list':
|
||||
cli.list_workflows()
|
||||
success = True
|
||||
elif args.command == 'help':
|
||||
cli.show_help()
|
||||
success = True
|
||||
else:
|
||||
cli.show_help()
|
||||
success = False
|
||||
|
||||
return 0 if success else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user