claude-plugin-wordpress-manager 2.2.0 → 2.3.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/.claude-plugin/plugin.json +2 -2
- package/CHANGELOG.md +26 -0
- package/agents/wp-content-strategist.md +25 -0
- package/agents/wp-ecommerce-manager.md +23 -0
- package/agents/wp-site-manager.md +26 -0
- package/docs/GUIDE.md +116 -28
- package/package.json +8 -3
- package/skills/wordpress-router/references/decision-tree.md +8 -2
- package/skills/wp-content/SKILL.md +1 -0
- package/skills/wp-content-attribution/SKILL.md +97 -0
- package/skills/wp-content-attribution/references/attribution-models.md +189 -0
- package/skills/wp-content-attribution/references/conversion-funnels.md +137 -0
- package/skills/wp-content-attribution/references/reporting-dashboards.md +199 -0
- package/skills/wp-content-attribution/references/roi-calculation.md +202 -0
- package/skills/wp-content-attribution/references/utm-tracking-setup.md +161 -0
- package/skills/wp-content-attribution/scripts/attribution_inspect.mjs +277 -0
- package/skills/wp-headless/SKILL.md +1 -0
- package/skills/wp-i18n/SKILL.md +1 -0
- package/skills/wp-multilang-network/SKILL.md +107 -0
- package/skills/wp-multilang-network/references/content-sync.md +182 -0
- package/skills/wp-multilang-network/references/hreflang-config.md +198 -0
- package/skills/wp-multilang-network/references/language-routing.md +234 -0
- package/skills/wp-multilang-network/references/network-architecture.md +119 -0
- package/skills/wp-multilang-network/references/seo-international.md +213 -0
- package/skills/wp-multilang-network/scripts/multilang_inspect.mjs +308 -0
- package/skills/wp-multisite/SKILL.md +1 -0
- package/skills/wp-programmatic-seo/SKILL.md +97 -0
- package/skills/wp-programmatic-seo/references/data-sources.md +200 -0
- package/skills/wp-programmatic-seo/references/location-seo.md +134 -0
- package/skills/wp-programmatic-seo/references/product-seo.md +147 -0
- package/skills/wp-programmatic-seo/references/technical-seo.md +197 -0
- package/skills/wp-programmatic-seo/references/template-architecture.md +125 -0
- package/skills/wp-programmatic-seo/scripts/programmatic_seo_inspect.mjs +264 -0
- package/skills/wp-woocommerce/SKILL.md +1 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* programmatic_seo_inspect.mjs — Detect programmatic SEO readiness.
|
|
3
|
+
*
|
|
4
|
+
* Scans for headless frontend, SEO plugins, content volume, custom post types,
|
|
5
|
+
* and WPGraphQL availability to assess readiness for programmatic page generation.
|
|
6
|
+
* Outputs a JSON report to stdout.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node programmatic_seo_inspect.mjs [--cwd=/path/to/check]
|
|
10
|
+
*
|
|
11
|
+
* Exit codes:
|
|
12
|
+
* 0 — programmatic SEO indicators detected
|
|
13
|
+
* 1 — no programmatic SEO indicators detected
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
import process from "node:process";
|
|
19
|
+
import { execSync } from "node:child_process";
|
|
20
|
+
|
|
21
|
+
const TOOL_VERSION = "1.0.0";
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Helpers
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
function statSafe(p) {
|
|
28
|
+
try {
|
|
29
|
+
return fs.statSync(p);
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readFileSafe(p) {
|
|
36
|
+
try {
|
|
37
|
+
return fs.readFileSync(p, "utf8");
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readJsonSafe(p) {
|
|
44
|
+
const raw = readFileSafe(p);
|
|
45
|
+
if (!raw) return null;
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(raw);
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function execSafe(cmd, cwd, timeoutMs = 5000) {
|
|
54
|
+
try {
|
|
55
|
+
return execSync(cmd, { encoding: "utf8", timeout: timeoutMs, cwd, stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readdirSafe(dir) {
|
|
62
|
+
try {
|
|
63
|
+
return fs.readdirSync(dir);
|
|
64
|
+
} catch {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Parse --cwd argument
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
function parseCwd() {
|
|
74
|
+
const cwdArg = process.argv.find((a) => a.startsWith("--cwd="));
|
|
75
|
+
return cwdArg ? cwdArg.slice(6) : process.cwd();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// Detect headless frontend
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
function detectHeadlessFrontend(cwd) {
|
|
83
|
+
const result = { detected: false, framework: null, location: null };
|
|
84
|
+
|
|
85
|
+
const frontendDirs = ["frontend", "client", "app", "web", "next", "nuxt", "."];
|
|
86
|
+
for (const dir of frontendDirs) {
|
|
87
|
+
const pkgPath = path.join(cwd, dir, "package.json");
|
|
88
|
+
const pkg = readJsonSafe(pkgPath);
|
|
89
|
+
if (!pkg) continue;
|
|
90
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
91
|
+
if (allDeps["next"]) { result.detected = true; result.framework = "Next.js"; result.location = dir; return result; }
|
|
92
|
+
if (allDeps["nuxt"] || allDeps["nuxt3"]) { result.detected = true; result.framework = "Nuxt"; result.location = dir; return result; }
|
|
93
|
+
if (allDeps["astro"]) { result.detected = true; result.framework = "Astro"; result.location = dir; return result; }
|
|
94
|
+
if (allDeps["gatsby"]) { result.detected = true; result.framework = "Gatsby"; result.location = dir; return result; }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Check for config files directly
|
|
98
|
+
const configFiles = [
|
|
99
|
+
{ file: "next.config.js", fw: "Next.js" },
|
|
100
|
+
{ file: "next.config.mjs", fw: "Next.js" },
|
|
101
|
+
{ file: "next.config.ts", fw: "Next.js" },
|
|
102
|
+
{ file: "nuxt.config.ts", fw: "Nuxt" },
|
|
103
|
+
{ file: "nuxt.config.js", fw: "Nuxt" },
|
|
104
|
+
{ file: "astro.config.mjs", fw: "Astro" },
|
|
105
|
+
{ file: "astro.config.ts", fw: "Astro" },
|
|
106
|
+
];
|
|
107
|
+
for (const { file, fw } of configFiles) {
|
|
108
|
+
if (statSafe(path.join(cwd, file))) {
|
|
109
|
+
result.detected = true;
|
|
110
|
+
result.framework = fw;
|
|
111
|
+
result.location = ".";
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Detect SEO plugins
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
function detectSeoPlugin(cwd) {
|
|
124
|
+
const result = { detected: false, plugin: null };
|
|
125
|
+
|
|
126
|
+
const pluginsDir = path.join(cwd, "wp-content", "plugins");
|
|
127
|
+
const seoPlugins = [
|
|
128
|
+
{ dir: "wordpress-seo", name: "Yoast SEO" },
|
|
129
|
+
{ dir: "seo-by-rank-math", name: "Rank Math" },
|
|
130
|
+
{ dir: "all-in-one-seo-pack", name: "All in One SEO" },
|
|
131
|
+
{ dir: "the-seo-framework-extension-manager", name: "The SEO Framework" },
|
|
132
|
+
];
|
|
133
|
+
|
|
134
|
+
for (const plugin of seoPlugins) {
|
|
135
|
+
if (statSafe(path.join(pluginsDir, plugin.dir))?.isDirectory()) {
|
|
136
|
+
result.detected = true;
|
|
137
|
+
result.plugin = plugin.name;
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Detect content counts by type
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
function detectContentCounts(cwd) {
|
|
150
|
+
const result = { posts: 0, pages: 0, products: 0, custom_post_types: [] };
|
|
151
|
+
|
|
152
|
+
// Try WP-CLI for accurate counts
|
|
153
|
+
const postCount = execSafe("wp post list --post_type=post --post_status=publish --format=count 2>/dev/null", cwd);
|
|
154
|
+
if (postCount) result.posts = parseInt(postCount) || 0;
|
|
155
|
+
|
|
156
|
+
const pageCount = execSafe("wp post list --post_type=page --post_status=publish --format=count 2>/dev/null", cwd);
|
|
157
|
+
if (pageCount) result.pages = parseInt(pageCount) || 0;
|
|
158
|
+
|
|
159
|
+
const productCount = execSafe("wp post list --post_type=product --post_status=publish --format=count 2>/dev/null", cwd);
|
|
160
|
+
if (productCount) result.products = parseInt(productCount) || 0;
|
|
161
|
+
|
|
162
|
+
// Detect custom post types via WP-CLI
|
|
163
|
+
const cptList = execSafe("wp post-type list --_builtin=0 --format=json 2>/dev/null", cwd);
|
|
164
|
+
if (cptList) {
|
|
165
|
+
try {
|
|
166
|
+
const cpts = JSON.parse(cptList);
|
|
167
|
+
result.custom_post_types = cpts.map((c) => c.name || c);
|
|
168
|
+
} catch { /* ignore parse errors */ }
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// Detect WPGraphQL
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
function detectWPGraphQL(cwd) {
|
|
179
|
+
const pluginsDir = path.join(cwd, "wp-content", "plugins");
|
|
180
|
+
if (statSafe(path.join(pluginsDir, "wp-graphql"))?.isDirectory()) return true;
|
|
181
|
+
|
|
182
|
+
const composer = readJsonSafe(path.join(cwd, "composer.json"));
|
|
183
|
+
if (composer) {
|
|
184
|
+
const allDeps = { ...composer.require, ...composer["require-dev"] };
|
|
185
|
+
if (allDeps["wp-graphql/wp-graphql"]) return true;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// Main
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
function main() {
|
|
196
|
+
const cwd = parseCwd();
|
|
197
|
+
|
|
198
|
+
if (!statSafe(cwd)?.isDirectory()) {
|
|
199
|
+
console.error(`Error: directory not found: ${cwd}`);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const headless = detectHeadlessFrontend(cwd);
|
|
204
|
+
const seo = detectSeoPlugin(cwd);
|
|
205
|
+
const content = detectContentCounts(cwd);
|
|
206
|
+
const hasWpgraphql = detectWPGraphQL(cwd);
|
|
207
|
+
const hasCustomPostTypes = content.custom_post_types.length > 0;
|
|
208
|
+
|
|
209
|
+
const detected = headless.detected || seo.detected || hasCustomPostTypes;
|
|
210
|
+
|
|
211
|
+
const report = {
|
|
212
|
+
tool: "programmatic_seo_inspect",
|
|
213
|
+
version: TOOL_VERSION,
|
|
214
|
+
cwd,
|
|
215
|
+
detected,
|
|
216
|
+
has_headless_frontend: headless.detected,
|
|
217
|
+
headless_framework: headless.framework,
|
|
218
|
+
headless_location: headless.location,
|
|
219
|
+
seo_plugin: seo.plugin,
|
|
220
|
+
content_counts: {
|
|
221
|
+
posts: content.posts,
|
|
222
|
+
pages: content.pages,
|
|
223
|
+
products: content.products,
|
|
224
|
+
},
|
|
225
|
+
has_custom_post_types: hasCustomPostTypes,
|
|
226
|
+
custom_post_types: content.custom_post_types,
|
|
227
|
+
has_wpgraphql: hasWpgraphql,
|
|
228
|
+
programmatic_seo_readiness: "unknown",
|
|
229
|
+
recommendations: [],
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Assess readiness
|
|
233
|
+
if (headless.detected && seo.detected && hasCustomPostTypes) {
|
|
234
|
+
report.programmatic_seo_readiness = "high";
|
|
235
|
+
} else if (headless.detected || (seo.detected && hasCustomPostTypes)) {
|
|
236
|
+
report.programmatic_seo_readiness = "medium";
|
|
237
|
+
} else if (seo.detected) {
|
|
238
|
+
report.programmatic_seo_readiness = "low";
|
|
239
|
+
} else {
|
|
240
|
+
report.programmatic_seo_readiness = "not_ready";
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Recommendations
|
|
244
|
+
if (!headless.detected) {
|
|
245
|
+
report.recommendations.push("No headless frontend detected. Install Next.js, Nuxt, or Astro for ISR/SSG page rendering.");
|
|
246
|
+
}
|
|
247
|
+
if (!seo.detected) {
|
|
248
|
+
report.recommendations.push("No SEO plugin detected. Install Yoast SEO or Rank Math for sitemap generation and meta management.");
|
|
249
|
+
}
|
|
250
|
+
if (!hasCustomPostTypes) {
|
|
251
|
+
report.recommendations.push("No custom post types found. Create CPTs to serve as structured data sources for programmatic pages.");
|
|
252
|
+
}
|
|
253
|
+
if (!hasWpgraphql && headless.detected) {
|
|
254
|
+
report.recommendations.push("WPGraphQL not installed. Consider it for efficient batch data fetching from headless frontend.");
|
|
255
|
+
}
|
|
256
|
+
if (content.posts === 0 && content.pages === 0 && content.products === 0) {
|
|
257
|
+
report.recommendations.push("No published content found. Create initial content or verify WP-CLI access for accurate counts.");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
console.log(JSON.stringify(report, null, 2));
|
|
261
|
+
process.exit(detected ? 0 : 1);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
main();
|
|
@@ -109,3 +109,4 @@ For complex multi-step WooCommerce operations, use the `wp-ecommerce-manager` ag
|
|
|
109
109
|
- `wp-audit` — Audit WooCommerce store security and performance
|
|
110
110
|
- `wp-backup` — Backup WooCommerce database and uploads
|
|
111
111
|
- `wp-webhooks` — WooCommerce webhook management (order/product/customer event notifications)
|
|
112
|
+
- `wp-content-attribution` — Content-commerce attribution, UTM tracking, revenue per content piece
|