2026-02-20 18:08:06 +01:00
2026-02-20 18:08:06 +01:00
2026-02-20 16:21:18 +01:00
2026-01-25 08:57:55 +01:00
+1
2026-01-27 22:28:35 +01:00
+1
2026-02-02 21:09:10 +01:00
+1
2026-02-02 17:43:53 +01:00
+1
2026-01-27 22:28:35 +01:00
2026-01-25 08:57:55 +01:00
+1
2026-01-26 10:06:53 +01:00
+1
2026-02-02 17:49:27 +01:00
+1
2026-01-26 17:07:35 +01:00
+1
2026-02-02 20:17:14 +01:00
+1
2026-01-26 17:07:35 +01:00
+1
2026-01-26 17:07:35 +01:00
+1
2026-01-26 17:07:35 +01:00
2026-02-20 16:21:18 +01:00

SEO Optimizer for WordPress

Production-ready Go application that automatically optimizes WordPress blog posts for SEO and generates new post ideas from Reddit using Claude or DeepSeek.

Features

  • Autonomous Optimization: Automatically selects and optimizes old blog posts
  • Budget-Conscious: Single comprehensive LLM API call per post
  • Multi-Language Support: Alternates between English and Spanish (Polylang)
  • Comprehensive SEO: Meta tags, content improvements, internal linking, FAQ blocks
  • Draft Revisions: Creates draft revisions for human approval (never auto-publishes)
  • Smart Selection: Respects cooldown periods and content quality criteria
  • REST API: Manual triggers and status monitoring
  • Idea Generation: Daily SEO-focused post ideas from r/kubernetes
  • Production-Ready: Docker, Kubernetes, graceful shutdown, structured logging

Architecture

┌─────────────────┐
│   Scheduler     │  Language rotation (EN → ES)
│  (2.5h cycle)   │
└────────┬────────┘
         │
         v
┌─────────────────┐
│  Post Selector  │  Select oldest posts (120+ days)
└────────┬────────┘  Content >= 500 words
         │           Cooldown: 90 days
         v
┌─────────────────┐
│  LLM Provider   │  Single comprehensive prompt
│ (Claude/DeepSeek) │  - SEO meta optimization
└────────┬────────┘  - Content improvements
         │           - Internal links (3-8)
         │           - FAQ blocks
         │           - Fact checking
         v
┌─────────────────┐
│Content Builder  │  Apply all optimizations
└────────┬────────┘  Generate Gutenberg blocks
         │
         v
┌─────────────────┐
│  WordPress API  │  Create draft revision
└─────────────────┘  (pending approval)

Quick Start

Prerequisites

  • Go 1.21+
  • Docker (optional)
  • WordPress with:
    • Application Password enabled
    • Polylang plugin
    • RankMath SEO plugin
    • REST API accessible

Setup

  1. Clone and configure:

    git clone <repository>
    cd WPSideKick
    make dev-setup
    
  2. Edit credentials:

    # Edit .env with your credentials
    nano .env
    

    Required:

    • WP_APP_PASSWORD: Generate in WordPress → Users → Profile → Application Passwords
    • CLAUDE_API_KEY (when using Claude) or DEEPSEEK_API_KEY (when using DeepSeek)
  3. Install dependencies:

    make deps
    
  4. Run locally:

    make run
    

Configuration

Environment Variables

Variable Description Default
WP_APP_PASSWORD WordPress Application Password Required
LLM_PROVIDER LLM provider (claude or deepseek) claude
CLAUDE_API_KEY Claude API key (when LLM_PROVIDER=claude) -
DEEPSEEK_API_KEY DeepSeek API key (when LLM_PROVIDER=deepseek) -
DEEPSEEK_BASE_URL DeepSeek API base URL https://api.deepseek.com
DRY_RUN Skip WordPress draft creation/publishing false
IDEAS_ENABLED Enable idea generation true
IDEAS_INTERVAL_HOURS Hours between idea runs 24
IDEAS_SUBREDDIT Subreddit to analyze kubernetes
IDEAS_LISTING Listing (top, hot, new) top
IDEAS_TIME_RANGE Time range for top day
IDEAS_MAX_REDDIT_POSTS Max Reddit posts per run 20
IDEAS_MAX_IDEAS Max ideas per run 5
IDEAS_STYLE_SAMPLES Recent posts used for style 3
IDEAS_MIN_SCORE Min Reddit score to include 25
IDEAS_MIN_COMMENTS Min Reddit comments to include 5
IDEAS_STORAGE_PATH Idea storage path ./data/ideas
OUTREACH_ENABLED Enable outreach suggestions true
OUTREACH_SUBREDDIT Subreddit for outreach kubernetes
OUTREACH_LISTING Listing (top, hot, new) top
OUTREACH_TIME_RANGE Time range for top day
OUTREACH_MAX_REDDIT_POSTS Max Reddit posts per run 20
OUTREACH_MAX_SUGGESTIONS Max suggestions per run 5
OUTREACH_MIN_SCORE Min Reddit score to include 25
OUTREACH_MIN_COMMENTS Min Reddit comments to include 5
OUTREACH_MAX_ARTICLES Max internal articles to consider 50
OUTREACH_STORAGE_PATH Outreach storage path ./data/outreach
NEWSLETTER_ENABLED Enable weekly newsletters true
NEWSLETTER_INTERVAL_HOURS Hours between runs 168
NEWSLETTER_FEED_URL RSS feed URL https://alexandre-vazquez.com/feed/
NEWSLETTER_SUBREDDIT Subreddit for context kubernetes
NEWSLETTER_LISTING Listing (top, hot, new) top
NEWSLETTER_TIME_RANGE Time range for top week
NEWSLETTER_MAX_FEED_ITEMS Max feed items 5
NEWSLETTER_MAX_REDDIT_POSTS Max Reddit posts 10
NEWSLETTER_MIN_SCORE Min Reddit score 10
NEWSLETTER_MIN_COMMENTS Min Reddit comments 2
NEWSLETTER_STORAGE_PATH Newsletter storage path ./data/newsletters
OPTIMIZATION_INTERVAL_HOURS Hours between optimizations 2.5
LOG_LEVEL Logging level (INFO/DEBUG) INFO
API_PORT REST API port 8080
WEBHOOK_URL Optional webhook for notifications -

Configuration File

Single-site setup: copy configs/config.example.yaml to configs/config.yaml and customize.

Multi-site setup: copy configs/multi.example.yaml to configs/config.yaml and customize the global block plus each entry in sites (including data_base_path to keep storage isolated).

For outreach, set outreach.min_score and outreach.min_comments per site so thresholds match subreddit traffic (for example, kubernetes can use higher values than paternidad).

wordpress:
  base_url: "https://your-blog.com"
  username: "admin"

runtime:
  dry_run: false

claude:
  model: "claude-sonnet-4-20250514"
  max_tokens: 4096
  temperature: 0.3

llm:
  provider: "claude"

deepseek:
  base_url: "https://api.deepseek.com"
  model: "deepseek-chat"
  max_tokens: 4096
  temperature: 0.3

ideas:
  enabled: true
  interval_hours: 24
  subreddit: "kubernetes"
  listing: "top"
  time_range: "day"
  max_reddit_posts: 20
  max_ideas: 5
  style_sample_posts: 3
  min_score: 25
  min_comments: 5
  base_path: "./data/ideas"

outreach:
  enabled: true
  subreddit: "kubernetes"
  listing: "top"
  time_range: "day"
  max_reddit_posts: 20
  max_suggestions: 5
  min_score: 25
  min_comments: 5
  max_articles: 50
  base_path: "./data/outreach"

newsletter:
  enabled: true
  interval_hours: 168
  feed_url: "https://alexandre-vazquez.com/feed/"
  subreddit: "kubernetes"
  listing: "top"
  time_range: "week"
  max_feed_items: 5
  max_reddit_posts: 10
  min_score: 10
  min_comments: 2
  base_path: "./data/newsletters"

scheduler:
  enabled: true
  interval_hours: 2.5
  languages: ["en", "es"]

optimization:
  min_content_length: 500
  min_age_days: 30
  max_age_days: 365
  reoptimization_cooldown_days: 180

storage:
  base_path: "./data/optimizations"

audit:
  base_path: "./data/audit"

CLI

# List new ideas
wp-sk-cli ideas

# Generate ideas now
wp-sk-cli ideas generate

# Turn an idea into EN+ES drafts
wp-sk-cli ideas draft <idea_id>

# Publish both drafts
wp-sk-cli ideas publish <idea_id>

# Delete an idea
wp-sk-cli ideas delete <idea_id>

# List outreach suggestions
wp-sk-cli outreach

# Generate outreach suggestions now
wp-sk-cli outreach generate

# Delete an outreach suggestion
wp-sk-cli outreach delete <id>

# List newsletter drafts
wp-sk-cli newsletter

# Generate newsletter now
wp-sk-cli newsletter generate

# Delete a newsletter draft
wp-sk-cli newsletter delete <id>

# Render a draft in readable text
wp-sk-cli pending text <draft_post_id>

# Show recent audit records
wp-sk-cli audit 100

# Interactive shell
wp-sk-cli shell

# Show CLI + server version
wp-sk-cli version
wp-sk-cli --version

Web UI

The server now hosts a minimal Web UI at:

http://localhost:8080/ui/

Docker Deployment

# Build and start
make docker-build
make docker-run

# View logs
make logs

# Stop
make docker-stop

Docker Run

docker build -t seo-optimizer .

docker run -d \
  --name seo-optimizer \
  -p 8080:8080 \
  -e WP_APP_PASSWORD="your-password" \
  -e CLAUDE_API_KEY="your-key" \
  -e OPTIMIZATION_INTERVAL_HOURS=2.5 \
  seo-optimizer

Kubernetes Deployment

# Edit secrets in deployment.yaml first
kubectl apply -f deployments/kubernetes/deployment.yaml

# Check status
kubectl get pods -l app=seo-optimizer
kubectl logs -f deployment/seo-optimizer

# Port-forward for local access
kubectl port-forward svc/seo-optimizer 8080:8080

REST API

Endpoints

Health Check

curl http://localhost:8080/api/v1/health
# Response: {"status":"ok","version":"1.0.0"}

Manual Optimization

curl -X POST http://localhost:8080/api/v1/optimize \
  -H "Content-Type: application/json" \
  -d '{"post_id": 123, "language": "es"}'
# Response: {"job_id":"uuid","status":"accepted"}

Check Status

curl http://localhost:8080/api/v1/status/uuid
# Response: {"status":"completed","post_id":123,...}

Pending Reviews

curl http://localhost:8080/api/v1/pending
# Response: {"count":2,"records":[...]}

Optimization Details (by Post ID)

curl http://localhost:8080/api/v1/optimization/123
# Response: { ... optimization record ... }

Apply Draft

curl -X POST http://localhost:8080/api/v1/apply-draft \
  -H "Content-Type: application/json" \
  -d '{"draft_post_id": 456}'
# Response: {"status":"applied","message":"Draft applied to original post and draft deleted"}

Optimization Records (File Storage)

Optimizations are stored on disk for audit and review:

  • Base path: storage.base_path (default ./data/optimizations)
  • Subfolders: pending/, applied/, rejected/, skipped/
  • Files: {post_id}_{unix_timestamp}.json containing the full LLM response

How It Works

Post Selection

The optimizer selects posts based on:

  • Age: Last modified between 30 and 365 days ago
  • Content: >= 500 words
  • Cooldown: Not optimized in last 12 months
  • Status: Published only
  • Order: Oldest first

Optimization Process

  1. SEO Meta: Optimized title (50-60 chars) and description (150-160 chars)
  2. Content Changes: Keyword injection, heading improvements, technical updates
  3. Internal Links: 3-8 relevant internal links with descriptive anchors
  4. FAQ Block: 4-6 questions in RankMath format (if appropriate)
  5. Validation: Fact-checking and broken link fixes
  6. Draft Creation: WordPress draft revision (requires manual approval)

Cost Tracking

  • Example (Claude): Sonnet 4.5 at $3/1M input tokens and $15/1M output tokens
  • Est. per post: 8K input + 2K output ≈ $0.054 (€0.05)
  • Budget: €10/month ≈ 200 posts
  • Interval: 2.5 hours ≈ 10 posts/day ≈ 300 posts/month

Monitoring

Logs

# Docker Compose
docker-compose logs -f

# Kubernetes
kubectl logs -f deployment/seo-optimizer

# JSON format (default)
{
  "time":"2026-01-24T10:30:00Z",
  "level":"INFO",
  "msg":"Optimization completed",
  "post_id":123,
  "cost_usd":0.054,
  "changes":8,
  "links":5,
  "duration_seconds":45.2
}

Health Check

# HTTP
curl http://localhost:8080/api/v1/health

# Docker
docker inspect --format='{{.State.Health.Status}}' seo-optimizer

Development

Build

# Local build
make build

# Production build (optimized)
make build-prod

# Run with debug logging
make run-debug

Testing

# Run all tests
make test

# With coverage
make test-coverage

# Quick tests
make test-quick

Code Quality

# Format code
make fmt

# Lint (requires golangci-lint)
make lint

# All checks
make all

Troubleshooting

WordPress Connection Issues

Problem: "WordPress API error: status 401"

Solution:

  1. Verify WordPress Application Password is correct
  2. Check WordPress REST API is accessible: curl https://your-blog.com/wp-json/wp/v2/posts
  3. Ensure username and password are properly set in .env

Claude API Errors (Claude Only)

Problem: "Claude API error: status 401"

Solution:

  1. Verify Claude API key is correct
  2. Check API key has sufficient credits
  3. Test API key: curl https://api.anthropic.com/v1/messages -H "x-api-key: YOUR_KEY"

No Posts Selected

Problem: "No eligible posts found for optimization"

Possible causes:

  • All posts are too recent (< 120 days old)
  • All posts were recently optimized (< 90 days ago)
  • Posts don't meet minimum word count (500 words)

Solution:

  • Adjust max_age_days or reoptimization_cooldown_days in config
  • Check post modification dates in WordPress

Budget Exceeded

Problem: Costs exceeding €10/month

Solution:

  • Increase interval_hours (e.g., 3.0 or 4.0)
  • Reduce optimization frequency
  • Monitor costs in logs: grep "cost_usd" logs/*.log

Security

  • No Auto-Publish: Creates draft revisions only
  • Local API: No authentication (assumes local/trusted network)
  • Credentials: Use environment variables, never commit secrets
  • Non-Root: Docker runs as non-root user (UID 1000)

License

[Add your license here]

Support

Roadmap

  • Prometheus metrics export
  • Advanced retry strategies
  • Image optimization suggestions
  • Schema markup generation
  • Multi-site support
  • Category-specific optimization strategies
S
Description
No description provided
Readme 706 KiB
Languages
Go 76%
JavaScript 14.5%
CSS 4.6%
HTML 3.8%
Makefile 0.8%
Other 0.3%