wpfleet 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/README.md +143 -0
- package/bin/wpfleet.js +2 -0
- package/package.json +45 -0
- package/src/ai/client.js +149 -0
- package/src/ai/seo-prompts.js +117 -0
- package/src/commands/ai-optimize.js +242 -0
- package/src/commands/ai-write.js +119 -0
- package/src/commands/health.js +100 -0
- package/src/commands/interlink.js +208 -0
- package/src/commands/publish.js +136 -0
- package/src/commands/report.js +108 -0
- package/src/commands/security.js +158 -0
- package/src/commands/sites.js +98 -0
- package/src/commands/stats.js +48 -0
- package/src/commands/traffic.js +87 -0
- package/src/commands/update.js +112 -0
- package/src/config.js +51 -0
- package/src/index.js +231 -0
- package/src/utils/display.js +41 -0
- package/src/utils/http.js +40 -0
- package/src/utils/wp-api.js +75 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## [1.0.0] — 2026-03-02
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Initial release
|
|
9
|
+
- **sites list** — List all configured WordPress sites with online/offline status
|
|
10
|
+
- **sites add** — Add a new WordPress site to the fleet
|
|
11
|
+
- **sites remove** — Remove a site from configuration
|
|
12
|
+
- **stats** — Fetch post/page counts and last publish date for all sites
|
|
13
|
+
- **health** — Check response time, SSL expiry, WP version, plugin count, pending updates
|
|
14
|
+
- **publish** — Publish a markdown/HTML article to a specific site
|
|
15
|
+
- **bulk-publish** — Publish articles to multiple sites in batch
|
|
16
|
+
- **interlink** — Add internal links between posts on a single site
|
|
17
|
+
- **crosslink** — Add cross-site links between posts across the fleet
|
|
18
|
+
- **traffic** — Display traffic stats (requires GA4 integration)
|
|
19
|
+
- **report** — Generate a full fleet health report in markdown
|
|
20
|
+
- **update** — Trigger WordPress core/plugin/theme updates remotely
|
|
21
|
+
- **security** — Audit security headers, exposed endpoints, and common vulnerabilities
|
|
22
|
+
- **ai-write** — AI-powered article generation with SEO optimization (BYOK OpenAI/Anthropic)
|
|
23
|
+
- **ai-optimize** — AI-powered SEO scoring and meta optimization for existing posts (BYOK)
|
|
24
|
+
- BYOK AI: supports OpenAI (`gpt-4o-mini` default) and Anthropic (`claude-3-5-haiku-latest`)
|
|
25
|
+
- YAML configuration (`wpfleet.yml`) for multi-site management
|
|
26
|
+
- Cost tracking for AI operations
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
- JSON parse error in ai-optimize when AI returns markdown-fenced responses
|
|
30
|
+
- `villasaintjean.com` requires `www.` prefix due to 301 redirect
|
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# ⚡ wpfleet
|
|
2
|
+
|
|
3
|
+
**Manage your WordPress network from the terminal.**
|
|
4
|
+
|
|
5
|
+
Stop juggling 20+ wp-admin tabs. One command to monitor, publish, interlink, and report across your entire WordPress fleet.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
$ wpfleet health
|
|
9
|
+
|
|
10
|
+
┌──────────────────┬──────────┬────────────┬────────────┬─────────┬─────────┐
|
|
11
|
+
│ Site │ Response │ SSL Expiry │ WP Version │ Plugins │ Updates │
|
|
12
|
+
├──────────────────┼──────────┼────────────┼────────────┼─────────┼─────────┤
|
|
13
|
+
│ myblog │ 245ms │ 78d │ 6.7.1 │ 12 │ 0 │
|
|
14
|
+
│ niche-site │ 1830ms │ 12d ⚠️ │ 6.7.1 │ 18 │ 3 🔴 │
|
|
15
|
+
│ affiliate-hub │ 380ms │ 156d │ 6.7.1 │ 9 │ 0 │
|
|
16
|
+
└──────────────────┴──────────┴────────────┴────────────┴─────────┴─────────┘
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g wpfleet
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
**1. Add your sites:**
|
|
28
|
+
```bash
|
|
29
|
+
wpfleet sites add
|
|
30
|
+
# → Interactive: name, URL, WP credentials
|
|
31
|
+
# → Auto-validates your WP REST API connection
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**2. See your fleet:**
|
|
35
|
+
```bash
|
|
36
|
+
wpfleet sites list
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**3. Check everything's running:**
|
|
40
|
+
```bash
|
|
41
|
+
wpfleet health
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
That's it. Your entire network in one terminal.
|
|
45
|
+
|
|
46
|
+
## Commands
|
|
47
|
+
|
|
48
|
+
### Fleet Management
|
|
49
|
+
|
|
50
|
+
| Command | What it does |
|
|
51
|
+
|---------|-------------|
|
|
52
|
+
| `wpfleet sites list` | All your sites, online/offline status |
|
|
53
|
+
| `wpfleet sites add` | Add a site (interactive, validates API) |
|
|
54
|
+
| `wpfleet sites remove <name>` | Remove a site |
|
|
55
|
+
|
|
56
|
+
### Monitoring
|
|
57
|
+
|
|
58
|
+
| Command | What it does |
|
|
59
|
+
|---------|-------------|
|
|
60
|
+
| `wpfleet health [site]` | Response time, SSL, WP version, plugin status |
|
|
61
|
+
| `wpfleet stats [site]` | Post count, page count, last published date |
|
|
62
|
+
|
|
63
|
+
### Publishing *(coming soon)*
|
|
64
|
+
|
|
65
|
+
| Command | What it does |
|
|
66
|
+
|---------|-------------|
|
|
67
|
+
| `wpfleet publish article.md --site myblog` | Publish markdown to any site |
|
|
68
|
+
| `wpfleet bulk-publish ./articles/ --distribute` | Distribute articles across your network |
|
|
69
|
+
| `wpfleet schedule article.md --date "2026-04-01 09:00"` | Schedule posts |
|
|
70
|
+
|
|
71
|
+
### Internal Linking *(coming soon)*
|
|
72
|
+
|
|
73
|
+
| Command | What it does |
|
|
74
|
+
|---------|-------------|
|
|
75
|
+
| `wpfleet interlink --site myblog` | Detect orphan pages, suggest internal links |
|
|
76
|
+
| `wpfleet interlink --auto` | Auto-insert relevant internal links |
|
|
77
|
+
| `wpfleet crosslink siteA → siteB` | Cross-site linking opportunities |
|
|
78
|
+
|
|
79
|
+
### Analytics *(coming soon)*
|
|
80
|
+
|
|
81
|
+
| Command | What it does |
|
|
82
|
+
|---------|-------------|
|
|
83
|
+
| `wpfleet traffic` | GA4 traffic across all sites in one table |
|
|
84
|
+
| `wpfleet top-pages --site myblog` | Most visited pages |
|
|
85
|
+
| `wpfleet report --month 2026-02 --pdf` | Auto-generated monthly PDF report |
|
|
86
|
+
|
|
87
|
+
### Maintenance *(coming soon)*
|
|
88
|
+
|
|
89
|
+
| Command | What it does |
|
|
90
|
+
|---------|-------------|
|
|
91
|
+
| `wpfleet update plugins --all` | Update plugins across the network |
|
|
92
|
+
| `wpfleet security-scan` | Check vulnerabilities, headers, permissions |
|
|
93
|
+
|
|
94
|
+
## Configuration
|
|
95
|
+
|
|
96
|
+
wpfleet uses a YAML config file. It looks for `wpfleet.yml` in the current directory, then `~/.wpfleet/config.yml`.
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
sites:
|
|
100
|
+
- name: myblog
|
|
101
|
+
url: https://myblog.com
|
|
102
|
+
wp_user: admin
|
|
103
|
+
wp_app_password: xxxx xxxx xxxx xxxx xxxx xxxx
|
|
104
|
+
|
|
105
|
+
- name: niche-site
|
|
106
|
+
url: https://niche-site.com
|
|
107
|
+
wp_user: admin
|
|
108
|
+
wp_app_password: yyyy yyyy yyyy yyyy yyyy yyyy
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### WordPress App Passwords
|
|
112
|
+
|
|
113
|
+
wpfleet uses [Application Passwords](https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/) (built into WordPress 5.6+). No plugins needed.
|
|
114
|
+
|
|
115
|
+
To create one:
|
|
116
|
+
1. Go to **wp-admin → Users → Profile**
|
|
117
|
+
2. Scroll to **Application Passwords**
|
|
118
|
+
3. Enter name: `wpfleet-cli`
|
|
119
|
+
4. Click **Add New Application Password**
|
|
120
|
+
5. Copy the password into your `wpfleet.yml`
|
|
121
|
+
|
|
122
|
+
## Who is this for?
|
|
123
|
+
|
|
124
|
+
- **SEO affiliates** managing 10-100+ niche sites
|
|
125
|
+
- **WordPress agencies** with multiple client sites
|
|
126
|
+
- **Content publishers** running blog networks
|
|
127
|
+
- **Anyone** tired of logging into wp-admin 20 times a day
|
|
128
|
+
|
|
129
|
+
## Pricing
|
|
130
|
+
|
|
131
|
+
| Plan | Sites | Price |
|
|
132
|
+
|------|-------|-------|
|
|
133
|
+
| **Free** | Up to 5 | $0 |
|
|
134
|
+
| **Pro** | Up to 50 | $49/mo |
|
|
135
|
+
| **Agency** | Unlimited | $199/mo |
|
|
136
|
+
|
|
137
|
+
## Built by
|
|
138
|
+
|
|
139
|
+
[Recognity](https://recognity.fr) — Digital consulting & automation, Paris.
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
MIT
|
package/bin/wpfleet.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wpfleet",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool to manage a network of WordPress blogs from the terminal",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"bin": {
|
|
8
|
+
"wpfleet": "./bin/wpfleet.js"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "node --test test/config.test.js test/publish.test.js test/interlink.test.js test/report.test.js test/security.test.js test/traffic.test.js test/update.test.js"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"axios": "^1.6.0",
|
|
18
|
+
"chalk": "^5.3.0",
|
|
19
|
+
"cli-table3": "^0.6.3",
|
|
20
|
+
"commander": "^12.0.0",
|
|
21
|
+
"gray-matter": "^4.0.3",
|
|
22
|
+
"inquirer": "^9.2.0",
|
|
23
|
+
"js-yaml": "^4.1.0",
|
|
24
|
+
"marked": "^17.0.3"
|
|
25
|
+
},
|
|
26
|
+
"author": "Recognity <anthony@recognity.fr> (https://recognity.fr)",
|
|
27
|
+
"homepage": "https://recognity.fr",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/recognity/wpfleet.git"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"wordpress",
|
|
34
|
+
"wp",
|
|
35
|
+
"cli",
|
|
36
|
+
"multi-site",
|
|
37
|
+
"fleet",
|
|
38
|
+
"management",
|
|
39
|
+
"seo",
|
|
40
|
+
"ai",
|
|
41
|
+
"blog",
|
|
42
|
+
"publish",
|
|
43
|
+
"bulk"
|
|
44
|
+
]
|
|
45
|
+
}
|
package/src/ai/client.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { loadConfig } from '../config.js';
|
|
4
|
+
|
|
5
|
+
// Pricing per 1M tokens (input / output)
|
|
6
|
+
const PRICING = {
|
|
7
|
+
'gpt-4o-mini': { input: 0.15, output: 0.60 },
|
|
8
|
+
'gpt-4o': { input: 5.00, output: 15.00 },
|
|
9
|
+
'claude-haiku-4-5-20251001': { input: 0.80, output: 4.00 },
|
|
10
|
+
'claude-haiku-4-5': { input: 0.80, output: 4.00 },
|
|
11
|
+
'claude-sonnet-4-6': { input: 3.00, output: 15.00 },
|
|
12
|
+
'claude-opus-4-6': { input: 15.00, output: 75.00 },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function getAiConfig() {
|
|
16
|
+
// Env vars take priority
|
|
17
|
+
if (process.env.OPENAI_API_KEY) {
|
|
18
|
+
return {
|
|
19
|
+
provider: 'openai',
|
|
20
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
21
|
+
model: process.env.OPENAI_MODEL || 'gpt-4o-mini',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
25
|
+
return {
|
|
26
|
+
provider: 'anthropic',
|
|
27
|
+
apiKey: process.env.ANTHROPIC_API_KEY,
|
|
28
|
+
model: process.env.ANTHROPIC_MODEL || 'claude-haiku-4-5-20251001',
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Config file fallback
|
|
33
|
+
try {
|
|
34
|
+
const config = loadConfig();
|
|
35
|
+
if (config.ai && config.ai.api_key) {
|
|
36
|
+
return {
|
|
37
|
+
provider: config.ai.provider || 'openai',
|
|
38
|
+
apiKey: config.ai.api_key,
|
|
39
|
+
model: config.ai.model || 'gpt-4o-mini',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
} catch {}
|
|
43
|
+
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function estimateCost(model, inputTokens, outputTokens) {
|
|
48
|
+
const pricing = PRICING[model];
|
|
49
|
+
if (!pricing) return null;
|
|
50
|
+
return (inputTokens * pricing.input + outputTokens * pricing.output) / 1_000_000;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function callOpenAI(config, systemPrompt, userPrompt, maxTokens) {
|
|
54
|
+
const res = await axios.post(
|
|
55
|
+
'https://api.openai.com/v1/chat/completions',
|
|
56
|
+
{
|
|
57
|
+
model: config.model,
|
|
58
|
+
messages: [
|
|
59
|
+
{ role: 'system', content: systemPrompt },
|
|
60
|
+
{ role: 'user', content: userPrompt },
|
|
61
|
+
],
|
|
62
|
+
max_tokens: maxTokens,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
headers: {
|
|
66
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
67
|
+
'Content-Type': 'application/json',
|
|
68
|
+
},
|
|
69
|
+
timeout: 120000,
|
|
70
|
+
validateStatus: () => true,
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
if (res.status !== 200) {
|
|
75
|
+
throw new Error(`OpenAI error ${res.status}: ${res.data?.error?.message || JSON.stringify(res.data)}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
content: res.data.choices[0].message.content,
|
|
80
|
+
inputTokens: res.data.usage.prompt_tokens,
|
|
81
|
+
outputTokens: res.data.usage.completion_tokens,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function callAnthropic(config, systemPrompt, userPrompt, maxTokens) {
|
|
86
|
+
const res = await axios.post(
|
|
87
|
+
'https://api.anthropic.com/v1/messages',
|
|
88
|
+
{
|
|
89
|
+
model: config.model,
|
|
90
|
+
max_tokens: maxTokens,
|
|
91
|
+
system: systemPrompt,
|
|
92
|
+
messages: [{ role: 'user', content: userPrompt }],
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
headers: {
|
|
96
|
+
'x-api-key': config.apiKey,
|
|
97
|
+
'anthropic-version': '2023-06-01',
|
|
98
|
+
'Content-Type': 'application/json',
|
|
99
|
+
},
|
|
100
|
+
timeout: 120000,
|
|
101
|
+
validateStatus: () => true,
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
if (res.status !== 200) {
|
|
106
|
+
throw new Error(`Anthropic error ${res.status}: ${res.data?.error?.message || JSON.stringify(res.data)}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
content: res.data.content[0].text,
|
|
111
|
+
inputTokens: res.data.usage.input_tokens,
|
|
112
|
+
outputTokens: res.data.usage.output_tokens,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export async function aiComplete({ systemPrompt, userPrompt, maxTokens = 2000 }) {
|
|
117
|
+
const config = getAiConfig();
|
|
118
|
+
if (!config) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
'No AI API key configured. Set OPENAI_API_KEY or ANTHROPIC_API_KEY, or add ai.api_key to wpfleet.yml'
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const inputEst = Math.ceil((systemPrompt.length + userPrompt.length) / 4);
|
|
125
|
+
const costEst = estimateCost(config.model, inputEst, maxTokens);
|
|
126
|
+
process.stderr.write(
|
|
127
|
+
chalk.dim(`AI: ${config.provider}/${config.model}`) +
|
|
128
|
+
(costEst !== null ? chalk.dim(` | Est. cost: $${costEst.toFixed(4)}`) : '') +
|
|
129
|
+
'\n'
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
let result;
|
|
133
|
+
if (config.provider === 'openai') {
|
|
134
|
+
result = await callOpenAI(config, systemPrompt, userPrompt, maxTokens);
|
|
135
|
+
} else if (config.provider === 'anthropic') {
|
|
136
|
+
result = await callAnthropic(config, systemPrompt, userPrompt, maxTokens);
|
|
137
|
+
} else {
|
|
138
|
+
throw new Error(`Unknown AI provider: ${config.provider}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const actualCost = estimateCost(config.model, result.inputTokens, result.outputTokens);
|
|
142
|
+
process.stderr.write(
|
|
143
|
+
chalk.dim(`AI: ${result.inputTokens} in / ${result.outputTokens} out`) +
|
|
144
|
+
(actualCost !== null ? chalk.dim(` | Cost: $${actualCost.toFixed(4)}`) : '') +
|
|
145
|
+
'\n'
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
return result.content;
|
|
149
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEO-specialized prompts for content generation and optimization.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export function articleGenerationPrompt({ topic, keywords = [], words = 1500, lang = 'en', siteContext = '' }) {
|
|
6
|
+
const kwList = keywords.length ? keywords.join(', ') : topic;
|
|
7
|
+
|
|
8
|
+
const langInstruction = lang === 'fr'
|
|
9
|
+
? 'Write in French. Follow French SEO rules: natural French phrasing, French stop words are acceptable in headings, accents in content but not in slugs.'
|
|
10
|
+
: lang === 'en'
|
|
11
|
+
? 'Write in English.'
|
|
12
|
+
: `Write in ${lang}.`;
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
system: `You are an expert SEO content writer. You create high-quality, well-structured articles optimized for search engines without keyword stuffing. You follow E-E-A-T principles (Experience, Expertise, Authoritativeness, Trustworthiness). ${langInstruction} Return ONLY the requested markdown, no commentary.`,
|
|
16
|
+
|
|
17
|
+
user: `Write a ${words}-word SEO-optimized article about: "${topic}"
|
|
18
|
+
|
|
19
|
+
Target keywords: ${kwList}
|
|
20
|
+
${siteContext ? `\nExisting site content (use for internal link suggestions):\n${siteContext}` : ''}
|
|
21
|
+
|
|
22
|
+
Return ONLY a markdown document with this exact structure:
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
title: [SEO title, 50-60 characters, include primary keyword]
|
|
26
|
+
meta_description: [150-160 characters, include primary keyword, compelling CTA]
|
|
27
|
+
slug: [url-friendly slug, no accents, hyphens only]
|
|
28
|
+
keywords: [${keywords.length ? keywords.map(k => `"${k}"`).join(', ') : `"${topic}"`}]
|
|
29
|
+
lang: ${lang}
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
# [H1 matching title]
|
|
33
|
+
|
|
34
|
+
[Intro paragraph — primary keyword in first 100 words, hook the reader]
|
|
35
|
+
|
|
36
|
+
## [H2 section title]
|
|
37
|
+
|
|
38
|
+
[content — 200-300 words]
|
|
39
|
+
|
|
40
|
+
## [H2 section title]
|
|
41
|
+
|
|
42
|
+
[content — 200-300 words]
|
|
43
|
+
|
|
44
|
+
## [H2 section title]
|
|
45
|
+
|
|
46
|
+
[content — 200-300 words]
|
|
47
|
+
|
|
48
|
+
## [Additional H2 sections as needed]
|
|
49
|
+
|
|
50
|
+
## Conclusion
|
|
51
|
+
|
|
52
|
+
[Summary paragraph with CTA]
|
|
53
|
+
|
|
54
|
+
## Internal Link Suggestions
|
|
55
|
+
|
|
56
|
+
- [anchor text] → [suggested page/topic on the site]
|
|
57
|
+
- [anchor text] → [suggested page/topic on the site]
|
|
58
|
+
|
|
59
|
+
Requirements:
|
|
60
|
+
- Primary keyword in: title, H1, first paragraph, at least 2 H2s, meta description
|
|
61
|
+
- 3-6 H2 sections total, H3 subsections where helpful
|
|
62
|
+
- Natural keyword density (1-2%), never stuffed
|
|
63
|
+
- Suggest 2-3 relevant internal links at the end
|
|
64
|
+
- Total length: ~${words} words (excluding front-matter)`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function metaOptimizationPrompt({ title, content, currentMeta = '', url = '' }) {
|
|
69
|
+
const stripped = content.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 2500);
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
system: 'You are an expert SEO specialist. Analyze blog post content and provide optimized meta tags with actionable recommendations. Return only valid JSON.',
|
|
73
|
+
|
|
74
|
+
user: `Analyze this WordPress post and return SEO optimization suggestions.
|
|
75
|
+
|
|
76
|
+
URL: ${url || '(not provided)'}
|
|
77
|
+
Current Title: ${title}
|
|
78
|
+
Current Meta Description: ${currentMeta || '(none)'}
|
|
79
|
+
Content: ${stripped}
|
|
80
|
+
|
|
81
|
+
Return JSON with this exact structure:
|
|
82
|
+
{
|
|
83
|
+
"score": <integer 0-100>,
|
|
84
|
+
"issues": ["issue 1", "issue 2"],
|
|
85
|
+
"optimized_title": "improved title (50-60 chars, include primary keyword)",
|
|
86
|
+
"optimized_meta": "improved meta description (150-160 chars, include keyword + CTA)",
|
|
87
|
+
"primary_keyword": "main keyword detected",
|
|
88
|
+
"keyword_suggestions": ["primary keyword", "secondary keyword"],
|
|
89
|
+
"recommendations": ["specific actionable fix 1", "specific actionable fix 2", "specific actionable fix 3"]
|
|
90
|
+
}`,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function batchScoringPrompt({ posts }) {
|
|
95
|
+
const list = posts.map((p, i) =>
|
|
96
|
+
`${i + 1}. ID:${p.id} | Title: "${p.title}" | Words: ${p.wordCount || '?'} | Has meta: ${p.hasMeta ? 'yes' : 'no'}`
|
|
97
|
+
).join('\n');
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
system: 'You are an SEO auditor. Score blog posts for optimization potential. Return only valid JSON.',
|
|
101
|
+
|
|
102
|
+
user: `Score these ${posts.length} posts for SEO optimization potential. Evaluate based on: title length and keyword presence, word count (aim for 800+), slug quality, and meta description presence.
|
|
103
|
+
|
|
104
|
+
Posts:
|
|
105
|
+
${list}
|
|
106
|
+
|
|
107
|
+
Return a JSON array (one object per post, same order):
|
|
108
|
+
[
|
|
109
|
+
{
|
|
110
|
+
"id": <post id>,
|
|
111
|
+
"score": <integer 0-100>,
|
|
112
|
+
"priority": "high|medium|low",
|
|
113
|
+
"top_issue": "main SEO problem in one sentence"
|
|
114
|
+
}
|
|
115
|
+
]`,
|
|
116
|
+
};
|
|
117
|
+
}
|