Files
WPSidekick/README.md
T
alexandrev-tibco 3a66d687e1 Minor updates
2026-02-20 16:21:18 +01:00

547 lines
14 KiB
Markdown

# 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**:
```bash
git clone <repository>
cd WPSideKick
make dev-setup
```
2. **Edit credentials**:
```bash
# 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**:
```bash
make deps
```
4. **Run locally**:
```bash
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`).
```yaml
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
```bash
# 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
### Docker Compose (Recommended)
```bash
# Build and start
make docker-build
make docker-run
# View logs
make logs
# Stop
make docker-stop
```
### Docker Run
```bash
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
```bash
# 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
```bash
curl http://localhost:8080/api/v1/health
# Response: {"status":"ok","version":"1.0.0"}
```
#### Manual Optimization
```bash
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
```bash
curl http://localhost:8080/api/v1/status/uuid
# Response: {"status":"completed","post_id":123,...}
```
#### Pending Reviews
```bash
curl http://localhost:8080/api/v1/pending
# Response: {"count":2,"records":[...]}
```
#### Optimization Details (by Post ID)
```bash
curl http://localhost:8080/api/v1/optimization/123
# Response: { ... optimization record ... }
```
#### Apply Draft
```bash
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
```bash
# 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
```bash
# HTTP
curl http://localhost:8080/api/v1/health
# Docker
docker inspect --format='{{.State.Health.Status}}' seo-optimizer
```
## Development
### Build
```bash
# Local build
make build
# Production build (optimized)
make build-prod
# Run with debug logging
make run-debug
```
### Testing
```bash
# Run all tests
make test
# With coverage
make test-coverage
# Quick tests
make test-quick
```
### Code Quality
```bash
# 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
- Issues: [GitHub Issues](https://github.com/your-repo/issues)
- Blog: https://alexandre-vazquez.com
- Contact: [Your contact info]
## Roadmap
- [ ] Prometheus metrics export
- [ ] Advanced retry strategies
- [ ] Image optimization suggestions
- [ ] Schema markup generation
- [ ] Multi-site support
- [ ] Category-specific optimization strategies