claude-plugin-wordpress-manager 1.5.0 → 1.7.1

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 (68) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/CHANGELOG.md +97 -0
  3. package/README.md +27 -13
  4. package/agents/wp-accessibility-auditor.md +206 -0
  5. package/agents/wp-content-strategist.md +18 -0
  6. package/agents/wp-deployment-engineer.md +34 -2
  7. package/agents/wp-performance-optimizer.md +12 -0
  8. package/agents/wp-security-auditor.md +20 -0
  9. package/agents/wp-security-hardener.md +266 -0
  10. package/agents/wp-site-manager.md +14 -0
  11. package/agents/wp-test-engineer.md +207 -0
  12. package/docs/guides/INDEX.md +46 -0
  13. package/docs/guides/wp-blog.md +590 -0
  14. package/docs/guides/wp-design-system.md +976 -0
  15. package/docs/guides/wp-ecommerce.md +786 -0
  16. package/docs/guides/wp-landing-page.md +762 -0
  17. package/docs/guides/wp-portfolio.md +713 -0
  18. package/docs/plans/2026-02-27-design-system-guide-design.md +30 -0
  19. package/docs/plans/2026-02-27-site-type-guides-design.md +44 -0
  20. package/package.json +2 -2
  21. package/skills/wordpress-router/references/decision-tree.md +12 -2
  22. package/skills/wp-accessibility/SKILL.md +170 -0
  23. package/skills/wp-accessibility/references/a11y-audit-tools.md +248 -0
  24. package/skills/wp-accessibility/references/a11y-testing.md +222 -0
  25. package/skills/wp-accessibility/references/block-a11y.md +247 -0
  26. package/skills/wp-accessibility/references/interactive-a11y.md +272 -0
  27. package/skills/wp-accessibility/references/media-a11y.md +254 -0
  28. package/skills/wp-accessibility/references/theme-a11y.md +309 -0
  29. package/skills/wp-audit/SKILL.md +4 -0
  30. package/skills/wp-block-development/SKILL.md +5 -0
  31. package/skills/wp-block-themes/SKILL.md +4 -0
  32. package/skills/wp-e2e-testing/SKILL.md +186 -0
  33. package/skills/wp-e2e-testing/references/ci-integration.md +174 -0
  34. package/skills/wp-e2e-testing/references/jest-wordpress.md +114 -0
  35. package/skills/wp-e2e-testing/references/phpunit-wordpress.md +141 -0
  36. package/skills/wp-e2e-testing/references/playwright-wordpress.md +108 -0
  37. package/skills/wp-e2e-testing/references/test-data-generation.md +127 -0
  38. package/skills/wp-e2e-testing/references/visual-regression.md +107 -0
  39. package/skills/wp-e2e-testing/references/wp-env-setup.md +97 -0
  40. package/skills/wp-e2e-testing/scripts/test_inspect.mjs +375 -0
  41. package/skills/wp-headless/SKILL.md +168 -0
  42. package/skills/wp-headless/references/api-layer-choice.md +160 -0
  43. package/skills/wp-headless/references/cors-config.md +245 -0
  44. package/skills/wp-headless/references/frontend-integration.md +331 -0
  45. package/skills/wp-headless/references/headless-auth.md +286 -0
  46. package/skills/wp-headless/references/webhooks.md +277 -0
  47. package/skills/wp-headless/references/wpgraphql.md +331 -0
  48. package/skills/wp-headless/scripts/headless_inspect.mjs +321 -0
  49. package/skills/wp-i18n/SKILL.md +170 -0
  50. package/skills/wp-i18n/references/js-i18n.md +201 -0
  51. package/skills/wp-i18n/references/multilingual-setup.md +219 -0
  52. package/skills/wp-i18n/references/php-i18n.md +196 -0
  53. package/skills/wp-i18n/references/rtl-support.md +206 -0
  54. package/skills/wp-i18n/references/translation-workflow.md +178 -0
  55. package/skills/wp-i18n/references/wpcli-i18n.md +177 -0
  56. package/skills/wp-i18n/scripts/i18n_inspect.mjs +330 -0
  57. package/skills/wp-interactivity-api/SKILL.md +4 -0
  58. package/skills/wp-plugin-development/SKILL.md +6 -0
  59. package/skills/wp-rest-api/SKILL.md +4 -0
  60. package/skills/wp-security/SKILL.md +179 -0
  61. package/skills/wp-security/references/api-restriction.md +147 -0
  62. package/skills/wp-security/references/authentication-hardening.md +105 -0
  63. package/skills/wp-security/references/filesystem-hardening.md +105 -0
  64. package/skills/wp-security/references/http-headers.md +105 -0
  65. package/skills/wp-security/references/incident-response.md +144 -0
  66. package/skills/wp-security/references/user-capabilities.md +115 -0
  67. package/skills/wp-security/references/wp-config-security.md +129 -0
  68. package/skills/wp-security/scripts/security_inspect.mjs +393 -0
@@ -0,0 +1,331 @@
1
+ # WPGraphQL
2
+
3
+ Use this file when setting up and using WPGraphQL for headless WordPress.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ wp plugin install wp-graphql --activate
9
+
10
+ # Verify
11
+ wp graphql --help
12
+ curl -s http://localhost:8888/graphql -H "Content-Type: application/json" \
13
+ -d '{"query": "{ generalSettings { title } }"}' | jq
14
+ ```
15
+
16
+ GraphQL IDE: `http://localhost:8888/wp-admin/admin.php?page=graphiql-ide`
17
+
18
+ ## Common queries
19
+
20
+ ### Posts
21
+
22
+ ```graphql
23
+ # List posts with pagination
24
+ query GetPosts($first: Int = 10, $after: String) {
25
+ posts(first: $first, after: $after) {
26
+ pageInfo {
27
+ hasNextPage
28
+ endCursor
29
+ }
30
+ nodes {
31
+ id
32
+ databaseId
33
+ title
34
+ slug
35
+ date
36
+ excerpt
37
+ content
38
+ uri
39
+ featuredImage {
40
+ node {
41
+ sourceUrl(size: LARGE)
42
+ altText
43
+ mediaDetails {
44
+ width
45
+ height
46
+ }
47
+ }
48
+ }
49
+ author {
50
+ node {
51
+ name
52
+ avatar {
53
+ url
54
+ }
55
+ }
56
+ }
57
+ categories {
58
+ nodes {
59
+ name
60
+ slug
61
+ }
62
+ }
63
+ tags {
64
+ nodes {
65
+ name
66
+ slug
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ ```
73
+
74
+ ### Single post by slug
75
+
76
+ ```graphql
77
+ query GetPost($slug: ID!) {
78
+ post(id: $slug, idType: SLUG) {
79
+ title
80
+ content
81
+ date
82
+ modified
83
+ seo {
84
+ title
85
+ metaDesc
86
+ opengraphImage {
87
+ sourceUrl
88
+ }
89
+ }
90
+ author {
91
+ node {
92
+ name
93
+ description
94
+ }
95
+ }
96
+ }
97
+ }
98
+ ```
99
+
100
+ ### Pages
101
+
102
+ ```graphql
103
+ query GetPage($uri: String!) {
104
+ pageBy(uri: $uri) {
105
+ title
106
+ content
107
+ template {
108
+ templateName
109
+ }
110
+ children {
111
+ nodes {
112
+ ... on Page {
113
+ title
114
+ uri
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ ### Menus
123
+
124
+ ```graphql
125
+ query GetMenu {
126
+ menus(where: { location: PRIMARY }) {
127
+ nodes {
128
+ menuItems(first: 50) {
129
+ nodes {
130
+ id
131
+ label
132
+ url
133
+ parentId
134
+ cssClasses
135
+ target
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+ ```
142
+
143
+ ### Custom Post Types
144
+
145
+ ```php
146
+ // Register CPT with GraphQL support
147
+ register_post_type('product', [
148
+ 'label' => 'Products',
149
+ 'public' => true,
150
+ 'show_in_graphql' => true,
151
+ 'graphql_single_name' => 'product',
152
+ 'graphql_plural_name' => 'products',
153
+ ]);
154
+ ```
155
+
156
+ ```graphql
157
+ query GetProducts {
158
+ products(first: 12) {
159
+ nodes {
160
+ title
161
+ slug
162
+ productFields { # ACF field group
163
+ price
164
+ sku
165
+ }
166
+ }
167
+ }
168
+ }
169
+ ```
170
+
171
+ ### Custom Taxonomies
172
+
173
+ ```php
174
+ register_taxonomy('product_category', 'product', [
175
+ 'label' => 'Product Categories',
176
+ 'show_in_graphql' => true,
177
+ 'graphql_single_name' => 'productCategory',
178
+ 'graphql_plural_name' => 'productCategories',
179
+ ]);
180
+ ```
181
+
182
+ ## ACF integration
183
+
184
+ ```bash
185
+ wp plugin install wpgraphql-acf --activate
186
+ ```
187
+
188
+ ACF fields are automatically exposed when the field group's "Show in GraphQL" setting is enabled.
189
+
190
+ ```graphql
191
+ query GetPostWithACF {
192
+ post(id: "hello-world", idType: SLUG) {
193
+ title
194
+ customFields { # ACF field group name (camelCase)
195
+ subtitle
196
+ heroImage {
197
+ sourceUrl
198
+ altText
199
+ }
200
+ features { # Repeater field
201
+ title
202
+ description
203
+ }
204
+ }
205
+ }
206
+ }
207
+ ```
208
+
209
+ ## Custom resolvers
210
+
211
+ ```php
212
+ // Add custom field to existing type
213
+ add_action('graphql_register_types', function() {
214
+ register_graphql_field('Post', 'readingTime', [
215
+ 'type' => 'Int',
216
+ 'description' => 'Estimated reading time in minutes',
217
+ 'resolve' => function($post) {
218
+ $content = get_post_field('post_content', $post->databaseId);
219
+ $word_count = str_word_count(strip_tags($content));
220
+ return max(1, ceil($word_count / 200));
221
+ },
222
+ ]);
223
+ });
224
+
225
+ // Add custom root query
226
+ add_action('graphql_register_types', function() {
227
+ register_graphql_field('RootQuery', 'siteOptions', [
228
+ 'type' => 'SiteOptions',
229
+ 'description' => 'Global site options',
230
+ 'resolve' => function() {
231
+ return [
232
+ 'phone' => get_option('site_phone'),
233
+ 'address' => get_option('site_address'),
234
+ ];
235
+ },
236
+ ]);
237
+
238
+ register_graphql_object_type('SiteOptions', [
239
+ 'fields' => [
240
+ 'phone' => ['type' => 'String'],
241
+ 'address' => ['type' => 'String'],
242
+ ],
243
+ ]);
244
+ });
245
+ ```
246
+
247
+ ## Mutations
248
+
249
+ ```graphql
250
+ # Create a comment (authenticated)
251
+ mutation CreateComment($input: CreateCommentInput!) {
252
+ createComment(input: $input) {
253
+ success
254
+ comment {
255
+ id
256
+ content
257
+ date
258
+ author {
259
+ node {
260
+ name
261
+ }
262
+ }
263
+ }
264
+ }
265
+ }
266
+ ```
267
+
268
+ Variables:
269
+ ```json
270
+ {
271
+ "input": {
272
+ "commentOn": 1,
273
+ "content": "Great post!",
274
+ "author": "John"
275
+ }
276
+ }
277
+ ```
278
+
279
+ ## Performance
280
+
281
+ ### Query complexity limits
282
+
283
+ WPGraphQL enforces query depth and complexity limits by default.
284
+
285
+ ```php
286
+ // Adjust limits in wp-config.php or plugin
287
+ add_filter('graphql_max_query_amount', function() {
288
+ return 100; // max nodes per query (default: 100)
289
+ });
290
+ ```
291
+
292
+ ### Persisted queries
293
+
294
+ ```php
295
+ // Register a persisted query
296
+ add_action('graphql_register_types', function() {
297
+ register_graphql_query_alias('homepage', '
298
+ query Homepage {
299
+ posts(first: 6) { nodes { title slug excerpt } }
300
+ menus(where: { location: PRIMARY }) { nodes { menuItems { nodes { label url } } } }
301
+ }
302
+ ');
303
+ });
304
+ ```
305
+
306
+ ### Object caching
307
+
308
+ WPGraphQL integrates with WordPress object cache. Use Redis or Memcached for production:
309
+
310
+ ```bash
311
+ wp plugin install wp-graphql-smart-cache --activate
312
+ ```
313
+
314
+ ## Verification
315
+
316
+ ```bash
317
+ # Test GraphQL endpoint
318
+ curl -s http://localhost:8888/graphql \
319
+ -H "Content-Type: application/json" \
320
+ -d '{"query": "{ posts(first: 1) { nodes { title } } }"}' | jq
321
+
322
+ # Check schema introspection
323
+ curl -s http://localhost:8888/graphql \
324
+ -H "Content-Type: application/json" \
325
+ -d '{"query": "{ __schema { types { name } } }"}' | jq '.data.__schema.types | length'
326
+
327
+ # Verify CPT is registered in schema
328
+ curl -s http://localhost:8888/graphql \
329
+ -H "Content-Type: application/json" \
330
+ -d '{"query": "{ __type(name: \"Product\") { name fields { name } } }"}' | jq
331
+ ```
@@ -0,0 +1,321 @@
1
+ /**
2
+ * headless_inspect.mjs — Detect headless WordPress configuration.
3
+ *
4
+ * Scans for WPGraphQL, CORS config, frontend framework integration,
5
+ * and decoupled architecture indicators.
6
+ * Outputs a JSON report to stdout.
7
+ *
8
+ * Usage:
9
+ * node headless_inspect.mjs [--cwd=/path/to/check]
10
+ *
11
+ * Exit codes:
12
+ * 0 — headless indicators detected
13
+ * 1 — no headless 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 WPGraphQL
80
+ // ---------------------------------------------------------------------------
81
+
82
+ function detectWPGraphQL(cwd) {
83
+ const result = { detected: false, plugins: [] };
84
+
85
+ const pluginsDir = path.join(cwd, "wp-content", "plugins");
86
+ const graphqlPlugins = [
87
+ { dir: "wp-graphql", name: "WPGraphQL" },
88
+ { dir: "wpgraphql-acf", name: "WPGraphQL for ACF" },
89
+ { dir: "wp-graphql-jwt-authentication", name: "WPGraphQL JWT Auth" },
90
+ { dir: "wp-graphql-smart-cache", name: "WPGraphQL Smart Cache" },
91
+ { dir: "wp-graphql-woocommerce", name: "WPGraphQL WooCommerce" },
92
+ { dir: "wp-gatsby", name: "WP Gatsby" },
93
+ ];
94
+
95
+ for (const plugin of graphqlPlugins) {
96
+ if (statSafe(path.join(pluginsDir, plugin.dir))?.isDirectory()) {
97
+ result.detected = true;
98
+ result.plugins.push(plugin.name);
99
+ }
100
+ }
101
+
102
+ // Check composer.json for WPGraphQL
103
+ const composer = readJsonSafe(path.join(cwd, "composer.json"));
104
+ if (composer) {
105
+ const allDeps = { ...composer.require, ...composer["require-dev"] };
106
+ if (allDeps["wp-graphql/wp-graphql"]) {
107
+ result.detected = true;
108
+ if (!result.plugins.includes("WPGraphQL")) result.plugins.push("WPGraphQL (composer)");
109
+ }
110
+ }
111
+
112
+ return result;
113
+ }
114
+
115
+ // ---------------------------------------------------------------------------
116
+ // Detect CORS configuration
117
+ // ---------------------------------------------------------------------------
118
+
119
+ function detectCORS(cwd) {
120
+ const result = { detected: false, sources: [] };
121
+
122
+ // Check .htaccess
123
+ const htaccess = readFileSafe(path.join(cwd, ".htaccess"));
124
+ if (htaccess && /Access-Control-Allow-Origin/i.test(htaccess)) {
125
+ result.detected = true;
126
+ result.sources.push(".htaccess");
127
+ }
128
+
129
+ // Check wp-config.php for CORS constants
130
+ const wpConfig = readFileSafe(path.join(cwd, "wp-config.php"));
131
+ if (wpConfig && /CORS|Access-Control/i.test(wpConfig)) {
132
+ result.detected = true;
133
+ result.sources.push("wp-config.php");
134
+ }
135
+
136
+ // Check PHP files for CORS headers
137
+ const corsPhp = execSafe(
138
+ `grep -rl --include="*.php" "Access-Control-Allow-Origin" . 2>/dev/null | head -5`,
139
+ cwd
140
+ );
141
+ if (corsPhp && corsPhp.length > 0) {
142
+ result.detected = true;
143
+ const files = corsPhp.split("\n").map((f) => path.relative(cwd, f));
144
+ result.sources.push(...files.filter((f) => !result.sources.includes(f)));
145
+ }
146
+
147
+ // Check nginx config (common locations)
148
+ for (const confPath of ["/etc/nginx/sites-enabled/default", "/etc/nginx/conf.d/default.conf"]) {
149
+ const content = readFileSafe(confPath);
150
+ if (content && /Access-Control-Allow-Origin/i.test(content)) {
151
+ result.detected = true;
152
+ result.sources.push(confPath);
153
+ }
154
+ }
155
+
156
+ return result;
157
+ }
158
+
159
+ // ---------------------------------------------------------------------------
160
+ // Detect frontend framework
161
+ // ---------------------------------------------------------------------------
162
+
163
+ function detectFrontend(cwd) {
164
+ const result = { detected: false, framework: null, location: null };
165
+
166
+ // Check for frontend directories
167
+ const frontendDirs = ["frontend", "client", "app", "web", "next", "nuxt"];
168
+ for (const dir of frontendDirs) {
169
+ const pkgPath = path.join(cwd, dir, "package.json");
170
+ const pkg = readJsonSafe(pkgPath);
171
+ if (pkg) {
172
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
173
+ if (allDeps["next"]) {
174
+ result.detected = true;
175
+ result.framework = "Next.js";
176
+ result.location = dir;
177
+ return result;
178
+ }
179
+ if (allDeps["nuxt"] || allDeps["nuxt3"]) {
180
+ result.detected = true;
181
+ result.framework = "Nuxt";
182
+ result.location = dir;
183
+ return result;
184
+ }
185
+ if (allDeps["astro"]) {
186
+ result.detected = true;
187
+ result.framework = "Astro";
188
+ result.location = dir;
189
+ return result;
190
+ }
191
+ if (allDeps["gatsby"]) {
192
+ result.detected = true;
193
+ result.framework = "Gatsby";
194
+ result.location = dir;
195
+ return result;
196
+ }
197
+ }
198
+ }
199
+
200
+ // Check root package.json
201
+ const rootPkg = readJsonSafe(path.join(cwd, "package.json"));
202
+ if (rootPkg) {
203
+ const allDeps = { ...rootPkg.dependencies, ...rootPkg.devDependencies };
204
+ if (allDeps["next"]) { result.detected = true; result.framework = "Next.js"; result.location = "."; }
205
+ else if (allDeps["nuxt"] || allDeps["nuxt3"]) { result.detected = true; result.framework = "Nuxt"; result.location = "."; }
206
+ else if (allDeps["astro"]) { result.detected = true; result.framework = "Astro"; result.location = "."; }
207
+ else if (allDeps["gatsby"]) { result.detected = true; result.framework = "Gatsby"; result.location = "."; }
208
+ else if (allDeps["gatsby-source-wordpress"]) { result.detected = true; result.framework = "Gatsby"; result.location = "."; }
209
+ }
210
+
211
+ return result;
212
+ }
213
+
214
+ // ---------------------------------------------------------------------------
215
+ // Detect headless indicators in WordPress
216
+ // ---------------------------------------------------------------------------
217
+
218
+ function detectHeadlessIndicators(cwd) {
219
+ const result = { indicators: [] };
220
+
221
+ // Check for headless theme (minimal or API-only themes)
222
+ const themeDir = path.join(cwd, "wp-content", "themes");
223
+ if (statSafe(themeDir)?.isDirectory()) {
224
+ const themes = readdirSafe(themeDir);
225
+ for (const theme of themes) {
226
+ const functionsPhp = readFileSafe(path.join(themeDir, theme, "functions.php"));
227
+ if (functionsPhp && /headless|decoupled|api.only/i.test(functionsPhp)) {
228
+ result.indicators.push(`Headless theme detected: ${theme}`);
229
+ }
230
+ }
231
+ }
232
+
233
+ // Check for webhook configuration
234
+ const wpConfig = readFileSafe(path.join(cwd, "wp-config.php"));
235
+ if (wpConfig) {
236
+ if (/HEADLESS_WEBHOOK/i.test(wpConfig)) {
237
+ result.indicators.push("Webhook configuration found in wp-config.php");
238
+ }
239
+ if (/HEADLESS_FRONTEND|FRONTEND_URL/i.test(wpConfig)) {
240
+ result.indicators.push("Frontend URL constant found in wp-config.php");
241
+ }
242
+ }
243
+
244
+ // Check for REST API customizations
245
+ const restCustom = execSafe(
246
+ `grep -rl --include="*.php" "register_rest_route" . 2>/dev/null | wc -l`,
247
+ cwd
248
+ );
249
+ if (restCustom && parseInt(restCustom) > 3) {
250
+ result.indicators.push(`${restCustom} files with custom REST routes detected`);
251
+ }
252
+
253
+ // Check for headless plugins
254
+ const pluginsDir = path.join(cwd, "wp-content", "plugins");
255
+ const headlessPlugins = [
256
+ { dir: "faust-wordpress", name: "Faust.js (WP Engine)" },
257
+ { dir: "atlas-content-modeler", name: "Atlas Content Modeler" },
258
+ { dir: "wp-gatsby", name: "WP Gatsby" },
259
+ { dir: "headless-mode", name: "Headless Mode" },
260
+ ];
261
+
262
+ for (const plugin of headlessPlugins) {
263
+ if (statSafe(path.join(pluginsDir, plugin.dir))?.isDirectory()) {
264
+ result.indicators.push(`Plugin: ${plugin.name}`);
265
+ }
266
+ }
267
+
268
+ return result;
269
+ }
270
+
271
+ // ---------------------------------------------------------------------------
272
+ // Main
273
+ // ---------------------------------------------------------------------------
274
+
275
+ function main() {
276
+ const cwd = parseCwd();
277
+
278
+ if (!statSafe(cwd)?.isDirectory()) {
279
+ console.error(`Error: directory not found: ${cwd}`);
280
+ process.exit(1);
281
+ }
282
+
283
+ const graphql = detectWPGraphQL(cwd);
284
+ const cors = detectCORS(cwd);
285
+ const frontend = detectFrontend(cwd);
286
+ const indicators = detectHeadlessIndicators(cwd);
287
+
288
+ const detected = graphql.detected || cors.detected || frontend.detected || indicators.indicators.length > 0;
289
+
290
+ const report = {
291
+ tool: "headless_inspect",
292
+ version: TOOL_VERSION,
293
+ cwd,
294
+ detected,
295
+ graphql,
296
+ cors,
297
+ frontend,
298
+ indicators: indicators.indicators,
299
+ apiLayer: graphql.detected ? "WPGraphQL" : "REST API",
300
+ recommendations: [],
301
+ };
302
+
303
+ // Recommendations
304
+ if (frontend.detected && !graphql.detected && !cors.detected) {
305
+ report.recommendations.push("Frontend framework detected but no CORS or GraphQL setup found. Configure CORS headers for cross-origin API access.");
306
+ }
307
+ if (graphql.detected && !cors.detected) {
308
+ report.recommendations.push("WPGraphQL detected but no CORS configuration found. Add CORS headers for frontend access.");
309
+ }
310
+ if (frontend.framework === "Gatsby" && !graphql.detected) {
311
+ report.recommendations.push("Gatsby detected. Install WPGraphQL for optimal integration with gatsby-source-wordpress.");
312
+ }
313
+ if (detected && indicators.indicators.length === 0) {
314
+ report.recommendations.push("Consider adding a webhook system to notify the frontend of content changes.");
315
+ }
316
+
317
+ console.log(JSON.stringify(report, null, 2));
318
+ process.exit(detected ? 0 : 1);
319
+ }
320
+
321
+ main();