claude-plugin-wordpress-manager 2.2.1 → 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.
Files changed (33) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/CHANGELOG.md +21 -0
  3. package/agents/wp-content-strategist.md +25 -0
  4. package/agents/wp-ecommerce-manager.md +23 -0
  5. package/agents/wp-site-manager.md +26 -0
  6. package/package.json +8 -3
  7. package/skills/wordpress-router/references/decision-tree.md +8 -2
  8. package/skills/wp-content/SKILL.md +1 -0
  9. package/skills/wp-content-attribution/SKILL.md +97 -0
  10. package/skills/wp-content-attribution/references/attribution-models.md +189 -0
  11. package/skills/wp-content-attribution/references/conversion-funnels.md +137 -0
  12. package/skills/wp-content-attribution/references/reporting-dashboards.md +199 -0
  13. package/skills/wp-content-attribution/references/roi-calculation.md +202 -0
  14. package/skills/wp-content-attribution/references/utm-tracking-setup.md +161 -0
  15. package/skills/wp-content-attribution/scripts/attribution_inspect.mjs +277 -0
  16. package/skills/wp-headless/SKILL.md +1 -0
  17. package/skills/wp-i18n/SKILL.md +1 -0
  18. package/skills/wp-multilang-network/SKILL.md +107 -0
  19. package/skills/wp-multilang-network/references/content-sync.md +182 -0
  20. package/skills/wp-multilang-network/references/hreflang-config.md +198 -0
  21. package/skills/wp-multilang-network/references/language-routing.md +234 -0
  22. package/skills/wp-multilang-network/references/network-architecture.md +119 -0
  23. package/skills/wp-multilang-network/references/seo-international.md +213 -0
  24. package/skills/wp-multilang-network/scripts/multilang_inspect.mjs +308 -0
  25. package/skills/wp-multisite/SKILL.md +1 -0
  26. package/skills/wp-programmatic-seo/SKILL.md +97 -0
  27. package/skills/wp-programmatic-seo/references/data-sources.md +200 -0
  28. package/skills/wp-programmatic-seo/references/location-seo.md +134 -0
  29. package/skills/wp-programmatic-seo/references/product-seo.md +147 -0
  30. package/skills/wp-programmatic-seo/references/technical-seo.md +197 -0
  31. package/skills/wp-programmatic-seo/references/template-architecture.md +125 -0
  32. package/skills/wp-programmatic-seo/scripts/programmatic_seo_inspect.mjs +264 -0
  33. package/skills/wp-woocommerce/SKILL.md +1 -0
@@ -0,0 +1,125 @@
1
+ # Template Architecture
2
+
3
+ Use this file when designing page templates for headless WordPress programmatic SEO — custom post types as data source, URL structure, ISR/SSG configuration, and bulk content creation.
4
+
5
+ ## Custom Post Types as Data Source
6
+
7
+ WordPress CPTs are the ideal data backbone for programmatic pages:
8
+
9
+ ```php
10
+ // Register a CPT for programmatic content (e.g., locations)
11
+ register_post_type('location', [
12
+ 'public' => true,
13
+ 'show_in_rest' => true, // Required for headless access
14
+ 'supports' => ['title', 'editor', 'custom-fields', 'thumbnail'],
15
+ 'rewrite' => ['slug' => 'locations'],
16
+ 'has_archive' => true,
17
+ ]);
18
+ ```
19
+
20
+ **Required CPT fields for programmatic SEO:**
21
+ - Title → H1 and `<title>` tag
22
+ - Custom fields → template variables (city, service, price, etc.)
23
+ - Taxonomy → category/tag for clustering
24
+ - Featured image → OG image
25
+
26
+ ## URL Structure Design
27
+
28
+ | Pattern | Example | Use Case |
29
+ |---------|---------|----------|
30
+ | `/{service}/{city}` | `/plumbing/miami` | Service-area pages |
31
+ | `/{product}/{variant}` | `/shoes/red-size-10` | Product variants |
32
+ | `/{category}/{item}` | `/restaurants/joes-pizza` | Directory listings |
33
+ | `/{brand}/{model}/{year}` | `/toyota/camry/2024` | Multi-attribute pages |
34
+ | `/{topic}/{subtopic}` | `/learn/react-hooks` | Educational content |
35
+
36
+ **URL rules:**
37
+ - Lowercase, hyphenated slugs
38
+ - Max 3 levels deep (SEO best practice)
39
+ - Include primary keyword in first path segment
40
+ - Avoid query parameters for indexable pages
41
+
42
+ ## ISR Configuration (Headless Frontend)
43
+
44
+ ### Next.js (App Router)
45
+
46
+ ```javascript
47
+ // app/[service]/[city]/page.js
48
+ export async function generateStaticParams() {
49
+ const res = await fetch(`${WP_API}/wp/v2/location?per_page=100`);
50
+ const locations = await res.json();
51
+ return locations.map((loc) => ({
52
+ service: loc.acf.service_slug,
53
+ city: loc.slug,
54
+ }));
55
+ }
56
+
57
+ export const revalidate = 3600; // ISR: revalidate every hour
58
+ ```
59
+
60
+ ### Nuxt
61
+
62
+ ```javascript
63
+ // nuxt.config.ts
64
+ export default defineNuxtConfig({
65
+ routeRules: {
66
+ '/services/**': { isr: 3600 },
67
+ },
68
+ });
69
+ ```
70
+
71
+ ### Astro
72
+
73
+ ```javascript
74
+ // astro.config.mjs — hybrid mode for ISR-like behavior
75
+ export default defineConfig({
76
+ output: 'hybrid',
77
+ adapter: node({ mode: 'standalone' }),
78
+ });
79
+ ```
80
+
81
+ **Revalidate intervals by content type:**
82
+
83
+ | Content Type | Interval | Reason |
84
+ |-------------|----------|--------|
85
+ | Location pages | 3600s (1h) | Rarely change |
86
+ | Product pages | 900s (15m) | Price/stock updates |
87
+ | Blog/guides | 86400s (24h) | Stable content |
88
+ | Comparison pages | 3600s (1h) | Data-driven, periodic |
89
+
90
+ ## Template Variables
91
+
92
+ A programmatic page template maps CPT fields to HTML:
93
+
94
+ ```
95
+ Title: "{service_name} in {city_name} — {brand}"
96
+ Meta desc: "Professional {service_name} in {city_name}. {unique_selling_point}."
97
+ H1: "{service_name} in {city_name}"
98
+ Body: Template paragraph using {city_population}, {service_details}, {local_info}
99
+ Schema: LocalBusiness JSON-LD with {name}, {address}, {phone}, {geo}
100
+ ```
101
+
102
+ **Anti-pattern:** Avoid thin content — each page must have enough unique value to justify indexing. Minimum 300 words of meaningful, varied content per page.
103
+
104
+ ## Bulk Content Creation Workflow
105
+
106
+ 1. **Prepare data:** CSV or JSON with one row per page (city, service, attributes)
107
+ 2. **Create CPT entries:** Loop via WordPress REST API:
108
+
109
+ ```bash
110
+ # Using wp-rest-bridge create_content tool
111
+ for each row in data:
112
+ create_content(type="location", title=row.title, content=row.body, meta=row.fields)
113
+ ```
114
+
115
+ 3. **Assign taxonomies:** Tag each entry with relevant terms for clustering
116
+ 4. **Generate frontend paths:** Run `generateStaticParams` or equivalent build step
117
+ 5. **Verify:** Spot-check 5–10 pages for content quality and schema validity
118
+
119
+ ## Decision Checklist
120
+
121
+ 1. Is the data structured and repeatable? → Yes = proceed with CPT
122
+ 2. Will each page have unique, valuable content? → Yes = proceed; No = consolidate
123
+ 3. Is the URL pattern SEO-friendly and max 3 levels? → Verify before building
124
+ 4. Is ISR/SSG configured with appropriate revalidation? → Match to content freshness
125
+ 5. Are sitemaps generated for all programmatic pages? → See `technical-seo.md`
@@ -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