claude-plugin-wordpress-manager 2.6.0 → 2.9.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.
- package/CHANGELOG.md +61 -0
- package/agents/wp-monitoring-agent.md +44 -0
- package/agents/wp-site-manager.md +19 -0
- package/docs/GUIDE.md +145 -14
- package/docs/plans/2026-03-01-tier4-5-implementation.md +1783 -0
- package/docs/plans/2026-03-01-tier4-5-observability-automation-design.md +426 -0
- package/docs/plans/2026-03-01-wcop-reassessment-v2.6.0.md +403 -0
- package/hooks/hooks.json +9 -0
- package/package.json +10 -3
- package/servers/wp-rest-bridge/build/tools/cwv.d.ts +3 -0
- package/servers/wp-rest-bridge/build/tools/cwv.js +196 -0
- package/servers/wp-rest-bridge/build/tools/ga4.d.ts +3 -0
- package/servers/wp-rest-bridge/build/tools/ga4.js +323 -0
- package/servers/wp-rest-bridge/build/tools/index.js +15 -0
- package/servers/wp-rest-bridge/build/tools/plausible.d.ts +3 -0
- package/servers/wp-rest-bridge/build/tools/plausible.js +207 -0
- package/servers/wp-rest-bridge/build/tools/slack.d.ts +3 -0
- package/servers/wp-rest-bridge/build/tools/slack.js +129 -0
- package/servers/wp-rest-bridge/build/tools/wc-workflows.d.ts +3 -0
- package/servers/wp-rest-bridge/build/tools/wc-workflows.js +222 -0
- package/servers/wp-rest-bridge/build/wordpress.d.ts +18 -0
- package/servers/wp-rest-bridge/build/wordpress.js +139 -0
- package/skills/wordpress-router/SKILL.md +1 -1
- package/skills/wordpress-router/references/decision-tree.md +8 -2
- package/skills/wp-alerting/SKILL.md +142 -0
- package/skills/wp-alerting/references/alert-thresholds.md +79 -0
- package/skills/wp-alerting/references/escalation-paths.md +92 -0
- package/skills/wp-alerting/references/report-scheduling.md +142 -0
- package/skills/wp-alerting/references/slack-integration.md +109 -0
- package/skills/wp-alerting/scripts/alerting_inspect.mjs +150 -0
- package/skills/wp-analytics/SKILL.md +158 -0
- package/skills/wp-analytics/references/analytics-dashboards.md +83 -0
- package/skills/wp-analytics/references/cwv-monitoring.md +101 -0
- package/skills/wp-analytics/references/ga4-integration.md +76 -0
- package/skills/wp-analytics/references/plausible-setup.md +92 -0
- package/skills/wp-analytics/references/traffic-attribution.md +92 -0
- package/skills/wp-analytics/scripts/analytics_inspect.mjs +153 -0
- package/skills/wp-content-attribution/SKILL.md +1 -0
- package/skills/wp-content-optimization/SKILL.md +1 -0
- package/skills/wp-content-workflows/SKILL.md +142 -0
- package/skills/wp-content-workflows/references/content-lifecycle-hooks.md +131 -0
- package/skills/wp-content-workflows/references/multi-channel-actions.md +151 -0
- package/skills/wp-content-workflows/references/schedule-triggers.md +118 -0
- package/skills/wp-content-workflows/references/trigger-management.md +159 -0
- package/skills/wp-content-workflows/references/wp-action-hooks.md +114 -0
- package/skills/wp-content-workflows/scripts/workflow_inspect.mjs +202 -0
- package/skills/wp-monitoring/SKILL.md +2 -0
- package/skills/wp-search-console/SKILL.md +1 -0
- package/skills/wp-social-email/SKILL.md +1 -0
- package/skills/wp-webhooks/SKILL.md +1 -0
|
@@ -0,0 +1,1783 @@
|
|
|
1
|
+
# Tier 4+5 WCOP Implementation Plan (v2.7.0 → v2.9.0)
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Add Observability (analytics + CWV + alerting) and Automation (workflow templates + event triggers) to the wordpress-manager plugin, raising WCOP score from 8/10 to 8.8/10.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Three incremental releases adding MCP tool files (TypeScript in `servers/wp-rest-bridge/src/tools/`), skills (SKILL.md + reference files), detection scripts (.mjs), agent updates, router updates, and safety hooks. All follow existing Tier 3 patterns exactly (Zod schemas → Tool[] → handlers Record with has*/make* guards).
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, `googleapis` npm package (GA4 Data API, CrUX API), `axios` (Plausible, PageSpeed Insights, Slack), Zod, MCP SDK.
|
|
10
|
+
|
|
11
|
+
**Design doc:** `docs/plans/2026-03-01-tier4-5-observability-automation-design.md`
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Release 1: v2.7.0 — Analytics (GA4 + Plausible + CWV)
|
|
16
|
+
|
|
17
|
+
### Task 1: Extend SiteConfig with analytics fields
|
|
18
|
+
|
|
19
|
+
**Files:**
|
|
20
|
+
- Modify: `servers/wp-rest-bridge/src/wordpress.ts:6-27` (SiteConfig interface)
|
|
21
|
+
- Modify: `servers/wp-rest-bridge/src/wordpress.ts:57-63` (module state — add client maps)
|
|
22
|
+
- Modify: `servers/wp-rest-bridge/src/wordpress.ts:114-141` (initWordPress loop — add GA4/Plausible init)
|
|
23
|
+
|
|
24
|
+
**Step 1: Add SiteConfig fields**
|
|
25
|
+
|
|
26
|
+
Add after the `gsc_site_url` line (line 26) in `wordpress.ts`:
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
// Google Analytics 4 (optional)
|
|
30
|
+
ga4_property_id?: string; // GA4 property (e.g., "properties/123456789")
|
|
31
|
+
ga4_service_account_key?: string; // Path to service account JSON (can reuse GSC key)
|
|
32
|
+
// Plausible Analytics (optional)
|
|
33
|
+
plausible_api_key?: string; // Plausible API key (Bearer token)
|
|
34
|
+
plausible_base_url?: string; // Default: "https://plausible.io" (or self-hosted)
|
|
35
|
+
// Google API key for public APIs (PageSpeed Insights, CrUX)
|
|
36
|
+
google_api_key?: string;
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Step 2: Add module state maps**
|
|
40
|
+
|
|
41
|
+
Add after `const sgSiteClients` (around line 63):
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
const plSiteClients = new Map<string, AxiosInstance>();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Step 3: Add Plausible client init function**
|
|
48
|
+
|
|
49
|
+
Add after `initSendGridClient` function:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
async function initPlausibleClient(id: string, apiKey: string, baseUrl?: string) {
|
|
53
|
+
const client = axios.create({
|
|
54
|
+
baseURL: (baseUrl || 'https://plausible.io') + '/api/v1/',
|
|
55
|
+
headers: {
|
|
56
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
57
|
+
},
|
|
58
|
+
timeout: DEFAULT_TIMEOUT_MS,
|
|
59
|
+
});
|
|
60
|
+
plSiteClients.set(id, client);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Step 4: Add init call in initWordPress loop**
|
|
65
|
+
|
|
66
|
+
In the `for (const site of sites)` loop (after SendGrid init, ~line 137):
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
if (site.plausible_api_key) {
|
|
70
|
+
await initPlausibleClient(site.id, site.plausible_api_key, site.plausible_base_url);
|
|
71
|
+
logToStderr(`Initialized Plausible for site: ${site.id}`);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Step 5: Add has/make/get functions for GA4, Plausible, CWV**
|
|
76
|
+
|
|
77
|
+
Add before the `// ── Plugin Repository` section (~line 670):
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// ── Google Analytics 4 Interface ─────────────────────────────────
|
|
81
|
+
|
|
82
|
+
const ga4AuthClients = new Map<string, any>();
|
|
83
|
+
|
|
84
|
+
export function hasGA4(siteId?: string): boolean {
|
|
85
|
+
const id = siteId || activeSiteId;
|
|
86
|
+
const sites = JSON.parse(process.env.WP_SITES_CONFIG || '[]');
|
|
87
|
+
const site = sites.find((s: SiteConfig) => s.id === id);
|
|
88
|
+
return !!(site?.ga4_property_id && site?.ga4_service_account_key);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function getGA4PropertyId(siteId?: string): string {
|
|
92
|
+
const id = siteId || activeSiteId;
|
|
93
|
+
const sites = JSON.parse(process.env.WP_SITES_CONFIG || '[]');
|
|
94
|
+
const site = sites.find((s: SiteConfig) => s.id === id);
|
|
95
|
+
if (!site?.ga4_property_id) {
|
|
96
|
+
throw new Error(`GA4 property not configured for site "${id}". Add ga4_property_id to WP_SITES_CONFIG.`);
|
|
97
|
+
}
|
|
98
|
+
return site.ga4_property_id;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function getGA4Auth(siteId?: string) {
|
|
102
|
+
const id = siteId || activeSiteId;
|
|
103
|
+
if (ga4AuthClients.has(id)) return ga4AuthClients.get(id);
|
|
104
|
+
|
|
105
|
+
const sites = JSON.parse(process.env.WP_SITES_CONFIG || '[]');
|
|
106
|
+
const site = sites.find((s: SiteConfig) => s.id === id);
|
|
107
|
+
if (!site?.ga4_service_account_key) {
|
|
108
|
+
throw new Error(`GA4 not configured for site "${id}". Add ga4_service_account_key to WP_SITES_CONFIG.`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const keyContent = JSON.parse(readFileSync(site.ga4_service_account_key, 'utf-8'));
|
|
112
|
+
const auth = new google.auth.GoogleAuth({
|
|
113
|
+
credentials: keyContent,
|
|
114
|
+
scopes: ['https://www.googleapis.com/auth/analytics.readonly'],
|
|
115
|
+
});
|
|
116
|
+
const authClient = await auth.getClient();
|
|
117
|
+
ga4AuthClients.set(id, authClient);
|
|
118
|
+
return authClient;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── Plausible Analytics Interface ────────────────────────────────
|
|
122
|
+
|
|
123
|
+
export function hasPlausible(siteId?: string): boolean {
|
|
124
|
+
const id = siteId || activeSiteId;
|
|
125
|
+
return plSiteClients.has(id);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export async function makePlausibleRequest(
|
|
129
|
+
method: string,
|
|
130
|
+
endpoint: string,
|
|
131
|
+
params?: Record<string, any>,
|
|
132
|
+
siteId?: string
|
|
133
|
+
): Promise<any> {
|
|
134
|
+
const id = siteId || activeSiteId;
|
|
135
|
+
const client = plSiteClients.get(id);
|
|
136
|
+
if (!client) {
|
|
137
|
+
throw new Error(`Plausible not configured for site "${id}". Add plausible_api_key to WP_SITES_CONFIG.`);
|
|
138
|
+
}
|
|
139
|
+
const limiter = getLimiter(id);
|
|
140
|
+
await limiter.acquire();
|
|
141
|
+
try {
|
|
142
|
+
const response = await client.request({ method, url: endpoint, params });
|
|
143
|
+
return response.data;
|
|
144
|
+
} finally {
|
|
145
|
+
limiter.release();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ── Core Web Vitals Interface (Google API Key) ───────────────────
|
|
150
|
+
|
|
151
|
+
export function hasGoogleApiKey(siteId?: string): boolean {
|
|
152
|
+
const id = siteId || activeSiteId;
|
|
153
|
+
const sites = JSON.parse(process.env.WP_SITES_CONFIG || '[]');
|
|
154
|
+
const site = sites.find((s: SiteConfig) => s.id === id);
|
|
155
|
+
return !!site?.google_api_key;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function getGoogleApiKey(siteId?: string): string {
|
|
159
|
+
const id = siteId || activeSiteId;
|
|
160
|
+
const sites = JSON.parse(process.env.WP_SITES_CONFIG || '[]');
|
|
161
|
+
const site = sites.find((s: SiteConfig) => s.id === id);
|
|
162
|
+
if (!site?.google_api_key) {
|
|
163
|
+
throw new Error(`Google API key not configured for site "${id}". Add google_api_key to WP_SITES_CONFIG.`);
|
|
164
|
+
}
|
|
165
|
+
return site.google_api_key;
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Step 6: Build to verify**
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
cd servers/wp-rest-bridge && npx tsc
|
|
173
|
+
```
|
|
174
|
+
Expected: No errors.
|
|
175
|
+
|
|
176
|
+
**Step 7: Commit**
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
git add servers/wp-rest-bridge/src/wordpress.ts
|
|
180
|
+
git commit -m "feat(wp-rest-bridge): extend SiteConfig with GA4, Plausible, CWV fields
|
|
181
|
+
|
|
182
|
+
- Add ga4_property_id, ga4_service_account_key to SiteConfig
|
|
183
|
+
- Add plausible_api_key, plausible_base_url to SiteConfig
|
|
184
|
+
- Add google_api_key for PageSpeed Insights / CrUX
|
|
185
|
+
- Add initPlausibleClient + plSiteClients map
|
|
186
|
+
- Add hasGA4/getGA4Auth/getGA4PropertyId (googleapis Service Account)
|
|
187
|
+
- Add hasPlausible/makePlausibleRequest (axios Bearer token)
|
|
188
|
+
- Add hasGoogleApiKey/getGoogleApiKey
|
|
189
|
+
|
|
190
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
### Task 2: Create GA4 tool file (6 tools)
|
|
196
|
+
|
|
197
|
+
**Files:**
|
|
198
|
+
- Create: `servers/wp-rest-bridge/src/tools/ga4.ts`
|
|
199
|
+
|
|
200
|
+
**Step 1: Write the GA4 tool file**
|
|
201
|
+
|
|
202
|
+
Follow the exact pattern from `gsc.ts`: import from `../wordpress.js`, Zod schemas, Tool[] array, handlers Record.
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
// src/tools/ga4.ts
|
|
206
|
+
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
207
|
+
import { hasGA4, getGA4Auth, getGA4PropertyId } from '../wordpress.js';
|
|
208
|
+
import { z } from 'zod';
|
|
209
|
+
|
|
210
|
+
// ── Zod Schemas ─────────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
const ga4RunReportSchema = z.object({
|
|
213
|
+
dimensions: z.array(z.string()).describe('Dimension names (e.g., ["date", "country", "pagePath"])'),
|
|
214
|
+
metrics: z.array(z.string()).describe('Metric names (e.g., ["sessions", "activeUsers", "screenPageViews"])'),
|
|
215
|
+
start_date: z.string().describe('Start date (YYYY-MM-DD or relative like "30daysAgo")'),
|
|
216
|
+
end_date: z.string().describe('End date (YYYY-MM-DD or "today")'),
|
|
217
|
+
limit: z.number().optional().default(100).describe('Max rows (default 100)'),
|
|
218
|
+
}).strict();
|
|
219
|
+
|
|
220
|
+
const ga4GetRealtimeSchema = z.object({
|
|
221
|
+
metrics: z.array(z.string()).optional().default(['activeUsers'])
|
|
222
|
+
.describe('Realtime metrics (default: ["activeUsers"])'),
|
|
223
|
+
dimensions: z.array(z.string()).optional()
|
|
224
|
+
.describe('Optional realtime dimensions (e.g., ["country", "unifiedScreenName"])'),
|
|
225
|
+
}).strict();
|
|
226
|
+
|
|
227
|
+
const ga4TopPagesSchema = z.object({
|
|
228
|
+
start_date: z.string().describe('Start date (YYYY-MM-DD or "30daysAgo")'),
|
|
229
|
+
end_date: z.string().describe('End date (YYYY-MM-DD or "today")'),
|
|
230
|
+
limit: z.number().optional().default(25).describe('Number of top pages (default 25)'),
|
|
231
|
+
}).strict();
|
|
232
|
+
|
|
233
|
+
const ga4TrafficSourcesSchema = z.object({
|
|
234
|
+
start_date: z.string().describe('Start date'),
|
|
235
|
+
end_date: z.string().describe('End date'),
|
|
236
|
+
limit: z.number().optional().default(25).describe('Number of sources (default 25)'),
|
|
237
|
+
}).strict();
|
|
238
|
+
|
|
239
|
+
const ga4UserDemographicsSchema = z.object({
|
|
240
|
+
start_date: z.string().describe('Start date'),
|
|
241
|
+
end_date: z.string().describe('End date'),
|
|
242
|
+
breakdown: z.enum(['country', 'deviceCategory', 'browser']).optional().default('country')
|
|
243
|
+
.describe('Breakdown dimension (default: country)'),
|
|
244
|
+
limit: z.number().optional().default(25).describe('Max rows (default 25)'),
|
|
245
|
+
}).strict();
|
|
246
|
+
|
|
247
|
+
const ga4ConversionEventsSchema = z.object({
|
|
248
|
+
start_date: z.string().describe('Start date'),
|
|
249
|
+
end_date: z.string().describe('End date'),
|
|
250
|
+
limit: z.number().optional().default(25).describe('Max events (default 25)'),
|
|
251
|
+
}).strict();
|
|
252
|
+
|
|
253
|
+
// ── Tool Definitions ────────────────────────────────────────────
|
|
254
|
+
|
|
255
|
+
export const ga4Tools: Tool[] = [
|
|
256
|
+
{
|
|
257
|
+
name: "ga4_run_report",
|
|
258
|
+
description: "Runs a custom GA4 report with specified dimensions and metrics",
|
|
259
|
+
inputSchema: {
|
|
260
|
+
type: "object",
|
|
261
|
+
properties: {
|
|
262
|
+
dimensions: { type: "array", items: { type: "string" }, description: "Dimension names (e.g., date, country, pagePath)" },
|
|
263
|
+
metrics: { type: "array", items: { type: "string" }, description: "Metric names (e.g., sessions, activeUsers)" },
|
|
264
|
+
start_date: { type: "string", description: "Start date (YYYY-MM-DD or 30daysAgo)" },
|
|
265
|
+
end_date: { type: "string", description: "End date (YYYY-MM-DD or today)" },
|
|
266
|
+
limit: { type: "number", description: "Max rows to return (default 100)" },
|
|
267
|
+
},
|
|
268
|
+
required: ["dimensions", "metrics", "start_date", "end_date"],
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: "ga4_get_realtime",
|
|
273
|
+
description: "Gets real-time active users and optional dimensions from GA4",
|
|
274
|
+
inputSchema: {
|
|
275
|
+
type: "object",
|
|
276
|
+
properties: {
|
|
277
|
+
metrics: { type: "array", items: { type: "string" }, description: "Realtime metrics (default: activeUsers)" },
|
|
278
|
+
dimensions: { type: "array", items: { type: "string" }, description: "Realtime dimensions (e.g., country)" },
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: "ga4_top_pages",
|
|
284
|
+
description: "Gets top pages by pageviews from GA4 (convenience shortcut)",
|
|
285
|
+
inputSchema: {
|
|
286
|
+
type: "object",
|
|
287
|
+
properties: {
|
|
288
|
+
start_date: { type: "string", description: "Start date" },
|
|
289
|
+
end_date: { type: "string", description: "End date" },
|
|
290
|
+
limit: { type: "number", description: "Number of top pages (default 25)" },
|
|
291
|
+
},
|
|
292
|
+
required: ["start_date", "end_date"],
|
|
293
|
+
},
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
name: "ga4_traffic_sources",
|
|
297
|
+
description: "Gets traffic sources breakdown by source/medium from GA4",
|
|
298
|
+
inputSchema: {
|
|
299
|
+
type: "object",
|
|
300
|
+
properties: {
|
|
301
|
+
start_date: { type: "string", description: "Start date" },
|
|
302
|
+
end_date: { type: "string", description: "End date" },
|
|
303
|
+
limit: { type: "number", description: "Number of sources (default 25)" },
|
|
304
|
+
},
|
|
305
|
+
required: ["start_date", "end_date"],
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
name: "ga4_user_demographics",
|
|
310
|
+
description: "Gets user demographic breakdown (country, device, browser) from GA4",
|
|
311
|
+
inputSchema: {
|
|
312
|
+
type: "object",
|
|
313
|
+
properties: {
|
|
314
|
+
start_date: { type: "string", description: "Start date" },
|
|
315
|
+
end_date: { type: "string", description: "End date" },
|
|
316
|
+
breakdown: { type: "string", enum: ["country", "deviceCategory", "browser"], description: "Breakdown type (default: country)" },
|
|
317
|
+
limit: { type: "number", description: "Max rows (default 25)" },
|
|
318
|
+
},
|
|
319
|
+
required: ["start_date", "end_date"],
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
name: "ga4_conversion_events",
|
|
324
|
+
description: "Gets conversion events and rates from GA4",
|
|
325
|
+
inputSchema: {
|
|
326
|
+
type: "object",
|
|
327
|
+
properties: {
|
|
328
|
+
start_date: { type: "string", description: "Start date" },
|
|
329
|
+
end_date: { type: "string", description: "End date" },
|
|
330
|
+
limit: { type: "number", description: "Max events (default 25)" },
|
|
331
|
+
},
|
|
332
|
+
required: ["start_date", "end_date"],
|
|
333
|
+
},
|
|
334
|
+
},
|
|
335
|
+
];
|
|
336
|
+
|
|
337
|
+
// ── Handlers ────────────────────────────────────────────────────
|
|
338
|
+
|
|
339
|
+
export const ga4Handlers: Record<string, Function> = {
|
|
340
|
+
ga4_run_report: async (params: z.infer<typeof ga4RunReportSchema>) => {
|
|
341
|
+
if (!hasGA4()) {
|
|
342
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "GA4 not configured. Add ga4_property_id and ga4_service_account_key to WP_SITES_CONFIG." }] } };
|
|
343
|
+
}
|
|
344
|
+
try {
|
|
345
|
+
const auth = await getGA4Auth();
|
|
346
|
+
const propertyId = getGA4PropertyId();
|
|
347
|
+
const { dimensions, metrics, start_date, end_date, limit } = params;
|
|
348
|
+
const analyticsdata = google.analyticsdata({ version: 'v1beta', auth });
|
|
349
|
+
const response = await analyticsdata.properties.runReport({
|
|
350
|
+
property: propertyId,
|
|
351
|
+
requestBody: {
|
|
352
|
+
dimensions: dimensions.map(d => ({ name: d })),
|
|
353
|
+
metrics: metrics.map(m => ({ name: m })),
|
|
354
|
+
dateRanges: [{ startDate: start_date, endDate: end_date }],
|
|
355
|
+
limit: limit || 100,
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] } };
|
|
359
|
+
} catch (error: any) {
|
|
360
|
+
const errorMessage = error.response?.data?.error?.message || error.message;
|
|
361
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error running GA4 report: ${errorMessage}` }] } };
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
ga4_get_realtime: async (params: z.infer<typeof ga4GetRealtimeSchema>) => {
|
|
366
|
+
if (!hasGA4()) {
|
|
367
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "GA4 not configured. Add ga4_property_id and ga4_service_account_key to WP_SITES_CONFIG." }] } };
|
|
368
|
+
}
|
|
369
|
+
try {
|
|
370
|
+
const auth = await getGA4Auth();
|
|
371
|
+
const propertyId = getGA4PropertyId();
|
|
372
|
+
const { metrics, dimensions } = params;
|
|
373
|
+
const analyticsdata = google.analyticsdata({ version: 'v1beta', auth });
|
|
374
|
+
const requestBody: Record<string, any> = {
|
|
375
|
+
metrics: (metrics || ['activeUsers']).map((m: string) => ({ name: m })),
|
|
376
|
+
};
|
|
377
|
+
if (dimensions) requestBody.dimensions = dimensions.map((d: string) => ({ name: d }));
|
|
378
|
+
const response = await analyticsdata.properties.runRealtimeReport({
|
|
379
|
+
property: propertyId,
|
|
380
|
+
requestBody,
|
|
381
|
+
});
|
|
382
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] } };
|
|
383
|
+
} catch (error: any) {
|
|
384
|
+
const errorMessage = error.response?.data?.error?.message || error.message;
|
|
385
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting GA4 realtime: ${errorMessage}` }] } };
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
|
|
389
|
+
ga4_top_pages: async (params: z.infer<typeof ga4TopPagesSchema>) => {
|
|
390
|
+
if (!hasGA4()) {
|
|
391
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "GA4 not configured. Add ga4_property_id and ga4_service_account_key to WP_SITES_CONFIG." }] } };
|
|
392
|
+
}
|
|
393
|
+
try {
|
|
394
|
+
const auth = await getGA4Auth();
|
|
395
|
+
const propertyId = getGA4PropertyId();
|
|
396
|
+
const { start_date, end_date, limit } = params;
|
|
397
|
+
const analyticsdata = google.analyticsdata({ version: 'v1beta', auth });
|
|
398
|
+
const response = await analyticsdata.properties.runReport({
|
|
399
|
+
property: propertyId,
|
|
400
|
+
requestBody: {
|
|
401
|
+
dimensions: [{ name: 'pagePath' }],
|
|
402
|
+
metrics: [{ name: 'screenPageViews' }, { name: 'activeUsers' }, { name: 'averageSessionDuration' }],
|
|
403
|
+
dateRanges: [{ startDate: start_date, endDate: end_date }],
|
|
404
|
+
orderBys: [{ metric: { metricName: 'screenPageViews' }, desc: true }],
|
|
405
|
+
limit: limit || 25,
|
|
406
|
+
},
|
|
407
|
+
});
|
|
408
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] } };
|
|
409
|
+
} catch (error: any) {
|
|
410
|
+
const errorMessage = error.response?.data?.error?.message || error.message;
|
|
411
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting top pages: ${errorMessage}` }] } };
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
|
|
415
|
+
ga4_traffic_sources: async (params: z.infer<typeof ga4TrafficSourcesSchema>) => {
|
|
416
|
+
if (!hasGA4()) {
|
|
417
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "GA4 not configured. Add ga4_property_id and ga4_service_account_key to WP_SITES_CONFIG." }] } };
|
|
418
|
+
}
|
|
419
|
+
try {
|
|
420
|
+
const auth = await getGA4Auth();
|
|
421
|
+
const propertyId = getGA4PropertyId();
|
|
422
|
+
const { start_date, end_date, limit } = params;
|
|
423
|
+
const analyticsdata = google.analyticsdata({ version: 'v1beta', auth });
|
|
424
|
+
const response = await analyticsdata.properties.runReport({
|
|
425
|
+
property: propertyId,
|
|
426
|
+
requestBody: {
|
|
427
|
+
dimensions: [{ name: 'sessionSource' }, { name: 'sessionMedium' }],
|
|
428
|
+
metrics: [{ name: 'sessions' }, { name: 'activeUsers' }, { name: 'conversions' }],
|
|
429
|
+
dateRanges: [{ startDate: start_date, endDate: end_date }],
|
|
430
|
+
orderBys: [{ metric: { metricName: 'sessions' }, desc: true }],
|
|
431
|
+
limit: limit || 25,
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] } };
|
|
435
|
+
} catch (error: any) {
|
|
436
|
+
const errorMessage = error.response?.data?.error?.message || error.message;
|
|
437
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting traffic sources: ${errorMessage}` }] } };
|
|
438
|
+
}
|
|
439
|
+
},
|
|
440
|
+
|
|
441
|
+
ga4_user_demographics: async (params: z.infer<typeof ga4UserDemographicsSchema>) => {
|
|
442
|
+
if (!hasGA4()) {
|
|
443
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "GA4 not configured. Add ga4_property_id and ga4_service_account_key to WP_SITES_CONFIG." }] } };
|
|
444
|
+
}
|
|
445
|
+
try {
|
|
446
|
+
const auth = await getGA4Auth();
|
|
447
|
+
const propertyId = getGA4PropertyId();
|
|
448
|
+
const { start_date, end_date, breakdown, limit } = params;
|
|
449
|
+
const analyticsdata = google.analyticsdata({ version: 'v1beta', auth });
|
|
450
|
+
const response = await analyticsdata.properties.runReport({
|
|
451
|
+
property: propertyId,
|
|
452
|
+
requestBody: {
|
|
453
|
+
dimensions: [{ name: breakdown || 'country' }],
|
|
454
|
+
metrics: [{ name: 'activeUsers' }, { name: 'sessions' }, { name: 'screenPageViews' }],
|
|
455
|
+
dateRanges: [{ startDate: start_date, endDate: end_date }],
|
|
456
|
+
orderBys: [{ metric: { metricName: 'activeUsers' }, desc: true }],
|
|
457
|
+
limit: limit || 25,
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] } };
|
|
461
|
+
} catch (error: any) {
|
|
462
|
+
const errorMessage = error.response?.data?.error?.message || error.message;
|
|
463
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting demographics: ${errorMessage}` }] } };
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
ga4_conversion_events: async (params: z.infer<typeof ga4ConversionEventsSchema>) => {
|
|
468
|
+
if (!hasGA4()) {
|
|
469
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "GA4 not configured. Add ga4_property_id and ga4_service_account_key to WP_SITES_CONFIG." }] } };
|
|
470
|
+
}
|
|
471
|
+
try {
|
|
472
|
+
const auth = await getGA4Auth();
|
|
473
|
+
const propertyId = getGA4PropertyId();
|
|
474
|
+
const { start_date, end_date, limit } = params;
|
|
475
|
+
const analyticsdata = google.analyticsdata({ version: 'v1beta', auth });
|
|
476
|
+
const response = await analyticsdata.properties.runReport({
|
|
477
|
+
property: propertyId,
|
|
478
|
+
requestBody: {
|
|
479
|
+
dimensions: [{ name: 'eventName' }],
|
|
480
|
+
metrics: [{ name: 'eventCount' }, { name: 'conversions' }, { name: 'totalRevenue' }],
|
|
481
|
+
dateRanges: [{ startDate: start_date, endDate: end_date }],
|
|
482
|
+
orderBys: [{ metric: { metricName: 'conversions' }, desc: true }],
|
|
483
|
+
limit: limit || 25,
|
|
484
|
+
},
|
|
485
|
+
});
|
|
486
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] } };
|
|
487
|
+
} catch (error: any) {
|
|
488
|
+
const errorMessage = error.response?.data?.error?.message || error.message;
|
|
489
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting conversion events: ${errorMessage}` }] } };
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
};
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
**Step 2: Build to verify**
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
cd servers/wp-rest-bridge && npx tsc
|
|
499
|
+
```
|
|
500
|
+
Expected: No errors.
|
|
501
|
+
|
|
502
|
+
**Step 3: Commit**
|
|
503
|
+
|
|
504
|
+
```bash
|
|
505
|
+
git add servers/wp-rest-bridge/src/tools/ga4.ts
|
|
506
|
+
git commit -m "feat(wp-rest-bridge): add GA4 Analytics tool file (6 tools)
|
|
507
|
+
|
|
508
|
+
- ga4_run_report: custom report with dimensions/metrics/date range
|
|
509
|
+
- ga4_get_realtime: real-time active users
|
|
510
|
+
- ga4_top_pages: top pages by pageviews (shortcut)
|
|
511
|
+
- ga4_traffic_sources: source/medium breakdown (shortcut)
|
|
512
|
+
- ga4_user_demographics: country/device/browser breakdown (shortcut)
|
|
513
|
+
- ga4_conversion_events: conversion events and rates (shortcut)
|
|
514
|
+
- Uses googleapis analyticsdata v1beta with Service Account auth
|
|
515
|
+
|
|
516
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
521
|
+
### Task 3: Create Plausible tool file (4 tools)
|
|
522
|
+
|
|
523
|
+
**Files:**
|
|
524
|
+
- Create: `servers/wp-rest-bridge/src/tools/plausible.ts`
|
|
525
|
+
|
|
526
|
+
**Step 1: Write the Plausible tool file**
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
529
|
+
// src/tools/plausible.ts
|
|
530
|
+
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
531
|
+
import { hasPlausible, makePlausibleRequest } from '../wordpress.js';
|
|
532
|
+
import { z } from 'zod';
|
|
533
|
+
|
|
534
|
+
// ── Zod Schemas ─────────────────────────────────────────────────
|
|
535
|
+
|
|
536
|
+
const plGetStatsSchema = z.object({
|
|
537
|
+
site_id: z.string().describe('Plausible site domain (e.g., "mysite.com")'),
|
|
538
|
+
period: z.enum(['day', '7d', '30d', 'month', '6mo', '12mo', 'custom']).optional().default('30d')
|
|
539
|
+
.describe('Time period (default: 30d)'),
|
|
540
|
+
date: z.string().optional().describe('Date or date range for custom period (YYYY-MM-DD or YYYY-MM-DD,YYYY-MM-DD)'),
|
|
541
|
+
metrics: z.string().optional().default('visitors,pageviews,bounce_rate,visit_duration')
|
|
542
|
+
.describe('Comma-separated metrics'),
|
|
543
|
+
}).strict();
|
|
544
|
+
|
|
545
|
+
const plGetTimeseriesSchema = z.object({
|
|
546
|
+
site_id: z.string().describe('Plausible site domain'),
|
|
547
|
+
period: z.enum(['day', '7d', '30d', 'month', '6mo', '12mo', 'custom']).optional().default('30d'),
|
|
548
|
+
date: z.string().optional(),
|
|
549
|
+
metrics: z.string().optional().default('visitors,pageviews'),
|
|
550
|
+
interval: z.enum(['date', 'month']).optional().default('date')
|
|
551
|
+
.describe('Data point interval (default: date)'),
|
|
552
|
+
}).strict();
|
|
553
|
+
|
|
554
|
+
const plGetBreakdownSchema = z.object({
|
|
555
|
+
site_id: z.string().describe('Plausible site domain'),
|
|
556
|
+
property: z.enum(['event:page', 'visit:source', 'visit:country', 'visit:device', 'visit:browser', 'visit:os', 'visit:utm_medium', 'visit:utm_source', 'visit:utm_campaign'])
|
|
557
|
+
.describe('Property to break down by'),
|
|
558
|
+
period: z.enum(['day', '7d', '30d', 'month', '6mo', '12mo', 'custom']).optional().default('30d'),
|
|
559
|
+
date: z.string().optional(),
|
|
560
|
+
metrics: z.string().optional().default('visitors,pageviews'),
|
|
561
|
+
limit: z.number().optional().default(100).describe('Max results (default 100)'),
|
|
562
|
+
}).strict();
|
|
563
|
+
|
|
564
|
+
const plGetRealtimeSchema = z.object({
|
|
565
|
+
site_id: z.string().describe('Plausible site domain'),
|
|
566
|
+
}).strict();
|
|
567
|
+
|
|
568
|
+
// ── Tool Definitions ────────────────────────────────────────────
|
|
569
|
+
|
|
570
|
+
export const plausibleTools: Tool[] = [
|
|
571
|
+
{
|
|
572
|
+
name: "pl_get_stats",
|
|
573
|
+
description: "Gets aggregate statistics from Plausible Analytics (visitors, pageviews, bounce rate, visit duration)",
|
|
574
|
+
inputSchema: {
|
|
575
|
+
type: "object",
|
|
576
|
+
properties: {
|
|
577
|
+
site_id: { type: "string", description: "Plausible site domain (e.g., mysite.com)" },
|
|
578
|
+
period: { type: "string", enum: ["day", "7d", "30d", "month", "6mo", "12mo", "custom"], description: "Time period (default: 30d)" },
|
|
579
|
+
date: { type: "string", description: "Date for custom period (YYYY-MM-DD or range)" },
|
|
580
|
+
metrics: { type: "string", description: "Comma-separated metrics (default: visitors,pageviews,bounce_rate,visit_duration)" },
|
|
581
|
+
},
|
|
582
|
+
required: ["site_id"],
|
|
583
|
+
},
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
name: "pl_get_timeseries",
|
|
587
|
+
description: "Gets time-series statistics from Plausible (daily/monthly data points)",
|
|
588
|
+
inputSchema: {
|
|
589
|
+
type: "object",
|
|
590
|
+
properties: {
|
|
591
|
+
site_id: { type: "string", description: "Plausible site domain" },
|
|
592
|
+
period: { type: "string", enum: ["day", "7d", "30d", "month", "6mo", "12mo", "custom"], description: "Time period" },
|
|
593
|
+
date: { type: "string", description: "Date for custom period" },
|
|
594
|
+
metrics: { type: "string", description: "Comma-separated metrics" },
|
|
595
|
+
interval: { type: "string", enum: ["date", "month"], description: "Data interval (default: date)" },
|
|
596
|
+
},
|
|
597
|
+
required: ["site_id"],
|
|
598
|
+
},
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
name: "pl_get_breakdown",
|
|
602
|
+
description: "Gets breakdown statistics from Plausible by property (page, source, country, device, etc.)",
|
|
603
|
+
inputSchema: {
|
|
604
|
+
type: "object",
|
|
605
|
+
properties: {
|
|
606
|
+
site_id: { type: "string", description: "Plausible site domain" },
|
|
607
|
+
property: { type: "string", enum: ["event:page", "visit:source", "visit:country", "visit:device", "visit:browser", "visit:os", "visit:utm_medium", "visit:utm_source", "visit:utm_campaign"], description: "Breakdown property" },
|
|
608
|
+
period: { type: "string", enum: ["day", "7d", "30d", "month", "6mo", "12mo", "custom"], description: "Time period" },
|
|
609
|
+
date: { type: "string", description: "Date for custom period" },
|
|
610
|
+
metrics: { type: "string", description: "Comma-separated metrics" },
|
|
611
|
+
limit: { type: "number", description: "Max results (default 100)" },
|
|
612
|
+
},
|
|
613
|
+
required: ["site_id", "property"],
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
name: "pl_get_realtime",
|
|
618
|
+
description: "Gets the current number of visitors on the site from Plausible",
|
|
619
|
+
inputSchema: {
|
|
620
|
+
type: "object",
|
|
621
|
+
properties: {
|
|
622
|
+
site_id: { type: "string", description: "Plausible site domain" },
|
|
623
|
+
},
|
|
624
|
+
required: ["site_id"],
|
|
625
|
+
},
|
|
626
|
+
},
|
|
627
|
+
];
|
|
628
|
+
|
|
629
|
+
// ── Handlers ────────────────────────────────────────────────────
|
|
630
|
+
|
|
631
|
+
export const plausibleHandlers: Record<string, Function> = {
|
|
632
|
+
pl_get_stats: async (params: z.infer<typeof plGetStatsSchema>) => {
|
|
633
|
+
if (!hasPlausible()) {
|
|
634
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "Plausible not configured. Add plausible_api_key to WP_SITES_CONFIG." }] } };
|
|
635
|
+
}
|
|
636
|
+
try {
|
|
637
|
+
const { site_id, period, date, metrics } = params;
|
|
638
|
+
const queryParams: Record<string, any> = { site_id, period: period || '30d' };
|
|
639
|
+
if (date) queryParams.date = date;
|
|
640
|
+
if (metrics) queryParams.metrics = metrics;
|
|
641
|
+
const response = await makePlausibleRequest('GET', 'stats/aggregate', queryParams);
|
|
642
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] } };
|
|
643
|
+
} catch (error: any) {
|
|
644
|
+
const errorMessage = error.response?.data?.error || error.message;
|
|
645
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting Plausible stats: ${errorMessage}` }] } };
|
|
646
|
+
}
|
|
647
|
+
},
|
|
648
|
+
|
|
649
|
+
pl_get_timeseries: async (params: z.infer<typeof plGetTimeseriesSchema>) => {
|
|
650
|
+
if (!hasPlausible()) {
|
|
651
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "Plausible not configured. Add plausible_api_key to WP_SITES_CONFIG." }] } };
|
|
652
|
+
}
|
|
653
|
+
try {
|
|
654
|
+
const { site_id, period, date, metrics, interval } = params;
|
|
655
|
+
const queryParams: Record<string, any> = { site_id, period: period || '30d' };
|
|
656
|
+
if (date) queryParams.date = date;
|
|
657
|
+
if (metrics) queryParams.metrics = metrics;
|
|
658
|
+
if (interval) queryParams.interval = interval;
|
|
659
|
+
const response = await makePlausibleRequest('GET', 'stats/timeseries', queryParams);
|
|
660
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] } };
|
|
661
|
+
} catch (error: any) {
|
|
662
|
+
const errorMessage = error.response?.data?.error || error.message;
|
|
663
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting Plausible timeseries: ${errorMessage}` }] } };
|
|
664
|
+
}
|
|
665
|
+
},
|
|
666
|
+
|
|
667
|
+
pl_get_breakdown: async (params: z.infer<typeof plGetBreakdownSchema>) => {
|
|
668
|
+
if (!hasPlausible()) {
|
|
669
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "Plausible not configured. Add plausible_api_key to WP_SITES_CONFIG." }] } };
|
|
670
|
+
}
|
|
671
|
+
try {
|
|
672
|
+
const { site_id, property, period, date, metrics, limit } = params;
|
|
673
|
+
const queryParams: Record<string, any> = { site_id, property, period: period || '30d' };
|
|
674
|
+
if (date) queryParams.date = date;
|
|
675
|
+
if (metrics) queryParams.metrics = metrics;
|
|
676
|
+
if (limit) queryParams.limit = limit;
|
|
677
|
+
const response = await makePlausibleRequest('GET', 'stats/breakdown', queryParams);
|
|
678
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] } };
|
|
679
|
+
} catch (error: any) {
|
|
680
|
+
const errorMessage = error.response?.data?.error || error.message;
|
|
681
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting Plausible breakdown: ${errorMessage}` }] } };
|
|
682
|
+
}
|
|
683
|
+
},
|
|
684
|
+
|
|
685
|
+
pl_get_realtime: async (params: z.infer<typeof plGetRealtimeSchema>) => {
|
|
686
|
+
if (!hasPlausible()) {
|
|
687
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "Plausible not configured. Add plausible_api_key to WP_SITES_CONFIG." }] } };
|
|
688
|
+
}
|
|
689
|
+
try {
|
|
690
|
+
const { site_id } = params;
|
|
691
|
+
const response = await makePlausibleRequest('GET', 'stats/realtime/visitors', { site_id });
|
|
692
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify({ visitors: response }, null, 2) }] } };
|
|
693
|
+
} catch (error: any) {
|
|
694
|
+
const errorMessage = error.response?.data?.error || error.message;
|
|
695
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting Plausible realtime: ${errorMessage}` }] } };
|
|
696
|
+
}
|
|
697
|
+
},
|
|
698
|
+
};
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
**Step 2: Build**
|
|
702
|
+
|
|
703
|
+
```bash
|
|
704
|
+
cd servers/wp-rest-bridge && npx tsc
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
**Step 3: Commit**
|
|
708
|
+
|
|
709
|
+
```bash
|
|
710
|
+
git add servers/wp-rest-bridge/src/tools/plausible.ts
|
|
711
|
+
git commit -m "feat(wp-rest-bridge): add Plausible Analytics tool file (4 tools)
|
|
712
|
+
|
|
713
|
+
- pl_get_stats: aggregate stats (visitors, pageviews, bounce rate)
|
|
714
|
+
- pl_get_timeseries: daily/monthly data points
|
|
715
|
+
- pl_get_breakdown: by page, source, country, device, UTM
|
|
716
|
+
- pl_get_realtime: current visitor count
|
|
717
|
+
- Uses Plausible REST API v1 with Bearer token auth
|
|
718
|
+
|
|
719
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
---
|
|
723
|
+
|
|
724
|
+
### Task 4: Create CWV tool file (4 tools)
|
|
725
|
+
|
|
726
|
+
**Files:**
|
|
727
|
+
- Create: `servers/wp-rest-bridge/src/tools/cwv.ts`
|
|
728
|
+
|
|
729
|
+
**Step 1: Write the CWV tool file**
|
|
730
|
+
|
|
731
|
+
```typescript
|
|
732
|
+
// src/tools/cwv.ts
|
|
733
|
+
import { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
734
|
+
import { hasGoogleApiKey, getGoogleApiKey } from '../wordpress.js';
|
|
735
|
+
import axios from 'axios';
|
|
736
|
+
import { z } from 'zod';
|
|
737
|
+
|
|
738
|
+
// ── Zod Schemas ─────────────────────────────────────────────────
|
|
739
|
+
|
|
740
|
+
const cwvAnalyzeUrlSchema = z.object({
|
|
741
|
+
url: z.string().describe('URL to analyze'),
|
|
742
|
+
strategy: z.enum(['mobile', 'desktop']).optional().default('mobile')
|
|
743
|
+
.describe('Analysis strategy (default: mobile)'),
|
|
744
|
+
categories: z.array(z.enum(['performance', 'accessibility', 'best-practices', 'seo'])).optional()
|
|
745
|
+
.describe('Lighthouse categories to include (default: performance only)'),
|
|
746
|
+
}).strict();
|
|
747
|
+
|
|
748
|
+
const cwvBatchAnalyzeSchema = z.object({
|
|
749
|
+
urls: z.array(z.string()).describe('Array of URLs to analyze (max 10)'),
|
|
750
|
+
strategy: z.enum(['mobile', 'desktop']).optional().default('mobile'),
|
|
751
|
+
}).strict();
|
|
752
|
+
|
|
753
|
+
const cwvGetFieldDataSchema = z.object({
|
|
754
|
+
url: z.string().optional().describe('Specific URL for field data (omit for origin-level data)'),
|
|
755
|
+
origin: z.string().optional().describe('Origin URL (e.g., https://mysite.com)'),
|
|
756
|
+
form_factor: z.enum(['PHONE', 'DESKTOP', 'TABLET', 'ALL_FORM_FACTORS']).optional().default('ALL_FORM_FACTORS'),
|
|
757
|
+
}).strict();
|
|
758
|
+
|
|
759
|
+
const cwvComparePagesSchema = z.object({
|
|
760
|
+
urls: z.array(z.string()).describe('URLs to compare (2-5)'),
|
|
761
|
+
strategy: z.enum(['mobile', 'desktop']).optional().default('mobile'),
|
|
762
|
+
}).strict();
|
|
763
|
+
|
|
764
|
+
// ── Tool Definitions ────────────────────────────────────────────
|
|
765
|
+
|
|
766
|
+
export const cwvTools: Tool[] = [
|
|
767
|
+
{
|
|
768
|
+
name: "cwv_analyze_url",
|
|
769
|
+
description: "Analyzes Core Web Vitals for a URL via PageSpeed Insights (LCP, INP, CLS, FCP, TTFB)",
|
|
770
|
+
inputSchema: {
|
|
771
|
+
type: "object",
|
|
772
|
+
properties: {
|
|
773
|
+
url: { type: "string", description: "URL to analyze" },
|
|
774
|
+
strategy: { type: "string", enum: ["mobile", "desktop"], description: "Strategy (default: mobile)" },
|
|
775
|
+
categories: { type: "array", items: { type: "string", enum: ["performance", "accessibility", "best-practices", "seo"] }, description: "Categories to include" },
|
|
776
|
+
},
|
|
777
|
+
required: ["url"],
|
|
778
|
+
},
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
name: "cwv_batch_analyze",
|
|
782
|
+
description: "Analyzes Core Web Vitals for multiple URLs (max 10) via PageSpeed Insights",
|
|
783
|
+
inputSchema: {
|
|
784
|
+
type: "object",
|
|
785
|
+
properties: {
|
|
786
|
+
urls: { type: "array", items: { type: "string" }, description: "URLs to analyze (max 10)" },
|
|
787
|
+
strategy: { type: "string", enum: ["mobile", "desktop"], description: "Strategy (default: mobile)" },
|
|
788
|
+
},
|
|
789
|
+
required: ["urls"],
|
|
790
|
+
},
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
name: "cwv_get_field_data",
|
|
794
|
+
description: "Gets real-user CWV field data from Chrome UX Report (28-day aggregate)",
|
|
795
|
+
inputSchema: {
|
|
796
|
+
type: "object",
|
|
797
|
+
properties: {
|
|
798
|
+
url: { type: "string", description: "Specific URL (omit for origin-level)" },
|
|
799
|
+
origin: { type: "string", description: "Origin URL (e.g., https://mysite.com)" },
|
|
800
|
+
form_factor: { type: "string", enum: ["PHONE", "DESKTOP", "TABLET", "ALL_FORM_FACTORS"], description: "Form factor (default: ALL)" },
|
|
801
|
+
},
|
|
802
|
+
},
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
name: "cwv_compare_pages",
|
|
806
|
+
description: "Compares Core Web Vitals across multiple pages and ranks optimization priority",
|
|
807
|
+
inputSchema: {
|
|
808
|
+
type: "object",
|
|
809
|
+
properties: {
|
|
810
|
+
urls: { type: "array", items: { type: "string" }, description: "URLs to compare (2-5)" },
|
|
811
|
+
strategy: { type: "string", enum: ["mobile", "desktop"], description: "Strategy (default: mobile)" },
|
|
812
|
+
},
|
|
813
|
+
required: ["urls"],
|
|
814
|
+
},
|
|
815
|
+
},
|
|
816
|
+
];
|
|
817
|
+
|
|
818
|
+
// ── Handlers ────────────────────────────────────────────────────
|
|
819
|
+
|
|
820
|
+
const PSI_BASE = 'https://www.googleapis.com/pagespeedonline/v5/runPagespeed';
|
|
821
|
+
const CRUX_BASE = 'https://chromeuxreport.googleapis.com/v1/records:queryRecord';
|
|
822
|
+
|
|
823
|
+
function extractCWV(lighthouse: any) {
|
|
824
|
+
const audits = lighthouse?.audits || {};
|
|
825
|
+
return {
|
|
826
|
+
lcp: audits['largest-contentful-paint']?.numericValue,
|
|
827
|
+
fcp: audits['first-contentful-paint']?.numericValue,
|
|
828
|
+
cls: audits['cumulative-layout-shift']?.numericValue,
|
|
829
|
+
inp: audits['interaction-to-next-paint']?.numericValue,
|
|
830
|
+
ttfb: audits['server-response-time']?.numericValue,
|
|
831
|
+
performance_score: lighthouse?.categories?.performance?.score,
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
export const cwvHandlers: Record<string, Function> = {
|
|
836
|
+
cwv_analyze_url: async (params: z.infer<typeof cwvAnalyzeUrlSchema>) => {
|
|
837
|
+
if (!hasGoogleApiKey()) {
|
|
838
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "Google API key not configured. Add google_api_key to WP_SITES_CONFIG." }] } };
|
|
839
|
+
}
|
|
840
|
+
try {
|
|
841
|
+
const { url, strategy, categories } = params;
|
|
842
|
+
const apiKey = getGoogleApiKey();
|
|
843
|
+
const queryParams: Record<string, any> = { url, key: apiKey, strategy: strategy || 'mobile' };
|
|
844
|
+
if (categories) {
|
|
845
|
+
categories.forEach(c => { queryParams[`category`] = c; });
|
|
846
|
+
} else {
|
|
847
|
+
queryParams.category = 'performance';
|
|
848
|
+
}
|
|
849
|
+
const response = await axios.get(PSI_BASE, { params: queryParams, timeout: 60000 });
|
|
850
|
+
const cwv = extractCWV(response.data.lighthouseResult);
|
|
851
|
+
const result = { url, strategy: strategy || 'mobile', cwv, fieldData: response.data.loadingExperience || null };
|
|
852
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] } };
|
|
853
|
+
} catch (error: any) {
|
|
854
|
+
const errorMessage = error.response?.data?.error?.message || error.message;
|
|
855
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error analyzing URL: ${errorMessage}` }] } };
|
|
856
|
+
}
|
|
857
|
+
},
|
|
858
|
+
|
|
859
|
+
cwv_batch_analyze: async (params: z.infer<typeof cwvBatchAnalyzeSchema>) => {
|
|
860
|
+
if (!hasGoogleApiKey()) {
|
|
861
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "Google API key not configured. Add google_api_key to WP_SITES_CONFIG." }] } };
|
|
862
|
+
}
|
|
863
|
+
try {
|
|
864
|
+
const { urls, strategy } = params;
|
|
865
|
+
const limitedUrls = urls.slice(0, 10);
|
|
866
|
+
const apiKey = getGoogleApiKey();
|
|
867
|
+
const results = [];
|
|
868
|
+
for (const url of limitedUrls) {
|
|
869
|
+
try {
|
|
870
|
+
const response = await axios.get(PSI_BASE, {
|
|
871
|
+
params: { url, key: apiKey, strategy: strategy || 'mobile', category: 'performance' },
|
|
872
|
+
timeout: 60000,
|
|
873
|
+
});
|
|
874
|
+
const cwv = extractCWV(response.data.lighthouseResult);
|
|
875
|
+
results.push({ url, cwv, status: 'ok' });
|
|
876
|
+
} catch (err: any) {
|
|
877
|
+
results.push({ url, status: 'error', error: err.response?.data?.error?.message || err.message });
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] } };
|
|
881
|
+
} catch (error: any) {
|
|
882
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error in batch analysis: ${error.message}` }] } };
|
|
883
|
+
}
|
|
884
|
+
},
|
|
885
|
+
|
|
886
|
+
cwv_get_field_data: async (params: z.infer<typeof cwvGetFieldDataSchema>) => {
|
|
887
|
+
if (!hasGoogleApiKey()) {
|
|
888
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "Google API key not configured. Add google_api_key to WP_SITES_CONFIG." }] } };
|
|
889
|
+
}
|
|
890
|
+
try {
|
|
891
|
+
const { url, origin, form_factor } = params;
|
|
892
|
+
const apiKey = getGoogleApiKey();
|
|
893
|
+
const requestBody: Record<string, any> = {};
|
|
894
|
+
if (url) requestBody.url = url;
|
|
895
|
+
else if (origin) requestBody.origin = origin;
|
|
896
|
+
else return { toolResult: { isError: true, content: [{ type: "text", text: "Provide either url or origin parameter." }] } };
|
|
897
|
+
if (form_factor && form_factor !== 'ALL_FORM_FACTORS') requestBody.formFactor = form_factor;
|
|
898
|
+
const response = await axios.post(`${CRUX_BASE}?key=${apiKey}`, requestBody, { timeout: 30000 });
|
|
899
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] } };
|
|
900
|
+
} catch (error: any) {
|
|
901
|
+
const errorMessage = error.response?.data?.error?.message || error.message;
|
|
902
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting CrUX data: ${errorMessage}` }] } };
|
|
903
|
+
}
|
|
904
|
+
},
|
|
905
|
+
|
|
906
|
+
cwv_compare_pages: async (params: z.infer<typeof cwvComparePagesSchema>) => {
|
|
907
|
+
if (!hasGoogleApiKey()) {
|
|
908
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "Google API key not configured. Add google_api_key to WP_SITES_CONFIG." }] } };
|
|
909
|
+
}
|
|
910
|
+
try {
|
|
911
|
+
const { urls, strategy } = params;
|
|
912
|
+
const limitedUrls = urls.slice(0, 5);
|
|
913
|
+
const apiKey = getGoogleApiKey();
|
|
914
|
+
const results = [];
|
|
915
|
+
for (const url of limitedUrls) {
|
|
916
|
+
try {
|
|
917
|
+
const response = await axios.get(PSI_BASE, {
|
|
918
|
+
params: { url, key: apiKey, strategy: strategy || 'mobile', category: 'performance' },
|
|
919
|
+
timeout: 60000,
|
|
920
|
+
});
|
|
921
|
+
const cwv = extractCWV(response.data.lighthouseResult);
|
|
922
|
+
results.push({ url, cwv });
|
|
923
|
+
} catch (err: any) {
|
|
924
|
+
results.push({ url, cwv: null, error: err.message });
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
// Rank by worst LCP
|
|
928
|
+
const ranked = results
|
|
929
|
+
.filter(r => r.cwv)
|
|
930
|
+
.sort((a, b) => (b.cwv!.lcp || 0) - (a.cwv!.lcp || 0))
|
|
931
|
+
.map((r, i) => ({ ...r, priority: i + 1 }));
|
|
932
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify({ comparison: ranked, worst_first: true }, null, 2) }] } };
|
|
933
|
+
} catch (error: any) {
|
|
934
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error comparing pages: ${error.message}` }] } };
|
|
935
|
+
}
|
|
936
|
+
},
|
|
937
|
+
};
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
**Step 2: Build**
|
|
941
|
+
|
|
942
|
+
```bash
|
|
943
|
+
cd servers/wp-rest-bridge && npx tsc
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
**Step 3: Commit**
|
|
947
|
+
|
|
948
|
+
```bash
|
|
949
|
+
git add servers/wp-rest-bridge/src/tools/cwv.ts
|
|
950
|
+
git commit -m "feat(wp-rest-bridge): add Core Web Vitals tool file (4 tools)
|
|
951
|
+
|
|
952
|
+
- cwv_analyze_url: PageSpeed Insights single URL (LCP, INP, CLS, FCP, TTFB)
|
|
953
|
+
- cwv_batch_analyze: batch analysis up to 10 URLs
|
|
954
|
+
- cwv_get_field_data: Chrome UX Report real-user data (28-day aggregate)
|
|
955
|
+
- cwv_compare_pages: compare CWV across pages with priority ranking
|
|
956
|
+
- Uses Google API key (no OAuth) for PageSpeed + CrUX APIs
|
|
957
|
+
|
|
958
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
---
|
|
962
|
+
|
|
963
|
+
### Task 5: Register analytics tools in index.ts and build
|
|
964
|
+
|
|
965
|
+
**Files:**
|
|
966
|
+
- Modify: `servers/wp-rest-bridge/src/tools/index.ts`
|
|
967
|
+
|
|
968
|
+
**Step 1: Add imports**
|
|
969
|
+
|
|
970
|
+
Add after the `gscTools` import line:
|
|
971
|
+
|
|
972
|
+
```typescript
|
|
973
|
+
import { ga4Tools, ga4Handlers } from './ga4.js';
|
|
974
|
+
import { plausibleTools, plausibleHandlers } from './plausible.js';
|
|
975
|
+
import { cwvTools, cwvHandlers } from './cwv.js';
|
|
976
|
+
```
|
|
977
|
+
|
|
978
|
+
**Step 2: Add to allTools array**
|
|
979
|
+
|
|
980
|
+
Add after `...gscTools`:
|
|
981
|
+
|
|
982
|
+
```typescript
|
|
983
|
+
...ga4Tools, // 6 tools
|
|
984
|
+
...plausibleTools, // 4 tools
|
|
985
|
+
...cwvTools, // 4 tools
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
**Step 3: Add to toolHandlers object**
|
|
989
|
+
|
|
990
|
+
Add after `...gscHandlers`:
|
|
991
|
+
|
|
992
|
+
```typescript
|
|
993
|
+
...ga4Handlers,
|
|
994
|
+
...plausibleHandlers,
|
|
995
|
+
...cwvHandlers,
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
**Step 4: Build**
|
|
999
|
+
|
|
1000
|
+
```bash
|
|
1001
|
+
cd servers/wp-rest-bridge && npx tsc
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
**Step 5: Commit**
|
|
1005
|
+
|
|
1006
|
+
```bash
|
|
1007
|
+
git add servers/wp-rest-bridge/src/tools/index.ts
|
|
1008
|
+
git commit -m "feat(wp-rest-bridge): register GA4 + Plausible + CWV tools in index
|
|
1009
|
+
|
|
1010
|
+
14 new tools registered (6 GA4 + 4 Plausible + 4 CWV), total 125
|
|
1011
|
+
|
|
1012
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
1013
|
+
```
|
|
1014
|
+
|
|
1015
|
+
---
|
|
1016
|
+
|
|
1017
|
+
### Task 6: Create analytics detection script
|
|
1018
|
+
|
|
1019
|
+
**Files:**
|
|
1020
|
+
- Create: `skills/wp-analytics/scripts/analytics_inspect.mjs`
|
|
1021
|
+
|
|
1022
|
+
**Step 1: Write the detection script**
|
|
1023
|
+
|
|
1024
|
+
Follow the exact pattern from `search_console_inspect.mjs`: helpers, detectors, main(), JSON report, exit code.
|
|
1025
|
+
|
|
1026
|
+
```javascript
|
|
1027
|
+
/**
|
|
1028
|
+
* analytics_inspect.mjs — Detect analytics configuration readiness.
|
|
1029
|
+
*
|
|
1030
|
+
* Checks WP_SITES_CONFIG for GA4, Plausible, and Google API key credentials.
|
|
1031
|
+
* Scans for analytics plugins and tracking code.
|
|
1032
|
+
*
|
|
1033
|
+
* Usage:
|
|
1034
|
+
* node analytics_inspect.mjs [--cwd=/path/to/project]
|
|
1035
|
+
*
|
|
1036
|
+
* Exit codes:
|
|
1037
|
+
* 0 — analytics configuration found
|
|
1038
|
+
* 1 — no analytics configuration found
|
|
1039
|
+
*/
|
|
1040
|
+
|
|
1041
|
+
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
1042
|
+
import { join, resolve } from 'node:path';
|
|
1043
|
+
import { argv, stdout, exit } from 'node:process';
|
|
1044
|
+
|
|
1045
|
+
// ---------------------------------------------------------------------------
|
|
1046
|
+
// Helpers
|
|
1047
|
+
// ---------------------------------------------------------------------------
|
|
1048
|
+
|
|
1049
|
+
function readFileSafe(filePath) {
|
|
1050
|
+
try { return readFileSync(filePath, 'utf-8'); } catch { return null; }
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
function existsSafe(filePath) {
|
|
1054
|
+
try { return existsSync(filePath); } catch { return false; }
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
function globDir(dirPath) {
|
|
1058
|
+
try { return readdirSync(dirPath); } catch { return []; }
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
// ---------------------------------------------------------------------------
|
|
1062
|
+
// Detectors
|
|
1063
|
+
// ---------------------------------------------------------------------------
|
|
1064
|
+
|
|
1065
|
+
function detectGA4Config() {
|
|
1066
|
+
const ga4 = { configured: false, indicators: [] };
|
|
1067
|
+
const raw = process.env.WP_SITES_CONFIG;
|
|
1068
|
+
if (!raw) return ga4;
|
|
1069
|
+
|
|
1070
|
+
let sites;
|
|
1071
|
+
try { sites = JSON.parse(raw); } catch { return ga4; }
|
|
1072
|
+
if (!Array.isArray(sites)) return ga4;
|
|
1073
|
+
|
|
1074
|
+
for (const site of sites) {
|
|
1075
|
+
const label = site.id || site.url || 'unknown';
|
|
1076
|
+
if (site.ga4_property_id) {
|
|
1077
|
+
ga4.configured = true;
|
|
1078
|
+
ga4.indicators.push(`ga4_property_id configured for ${label}`);
|
|
1079
|
+
}
|
|
1080
|
+
if (site.ga4_service_account_key) {
|
|
1081
|
+
ga4.indicators.push(`ga4_service_account_key configured for ${label}`);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
return ga4;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
function detectPlausibleConfig() {
|
|
1088
|
+
const pl = { configured: false, indicators: [] };
|
|
1089
|
+
const raw = process.env.WP_SITES_CONFIG;
|
|
1090
|
+
if (!raw) return pl;
|
|
1091
|
+
|
|
1092
|
+
let sites;
|
|
1093
|
+
try { sites = JSON.parse(raw); } catch { return pl; }
|
|
1094
|
+
if (!Array.isArray(sites)) return pl;
|
|
1095
|
+
|
|
1096
|
+
for (const site of sites) {
|
|
1097
|
+
const label = site.id || site.url || 'unknown';
|
|
1098
|
+
if (site.plausible_api_key) {
|
|
1099
|
+
pl.configured = true;
|
|
1100
|
+
pl.indicators.push(`plausible_api_key configured for ${label}`);
|
|
1101
|
+
}
|
|
1102
|
+
if (site.plausible_base_url) {
|
|
1103
|
+
pl.indicators.push(`plausible_base_url: ${site.plausible_base_url}`);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
return pl;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
function detectGoogleApiKey() {
|
|
1110
|
+
const cwv = { configured: false, indicators: [] };
|
|
1111
|
+
const raw = process.env.WP_SITES_CONFIG;
|
|
1112
|
+
if (!raw) return cwv;
|
|
1113
|
+
|
|
1114
|
+
let sites;
|
|
1115
|
+
try { sites = JSON.parse(raw); } catch { return cwv; }
|
|
1116
|
+
if (!Array.isArray(sites)) return cwv;
|
|
1117
|
+
|
|
1118
|
+
for (const site of sites) {
|
|
1119
|
+
const label = site.id || site.url || 'unknown';
|
|
1120
|
+
if (site.google_api_key) {
|
|
1121
|
+
cwv.configured = true;
|
|
1122
|
+
cwv.indicators.push(`google_api_key configured for ${label}`);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
return cwv;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
function detectAnalyticsPlugins(cwd) {
|
|
1129
|
+
const indicators = [];
|
|
1130
|
+
const pluginsDir = join(cwd, 'wp-content', 'plugins');
|
|
1131
|
+
const plugins = globDir(pluginsDir);
|
|
1132
|
+
|
|
1133
|
+
const analyticsPlugins = [
|
|
1134
|
+
'google-analytics-for-wordpress',
|
|
1135
|
+
'google-site-kit',
|
|
1136
|
+
'wp-google-analytics-events',
|
|
1137
|
+
'plausible-analytics',
|
|
1138
|
+
'koko-analytics',
|
|
1139
|
+
'matomo',
|
|
1140
|
+
'independent-analytics',
|
|
1141
|
+
];
|
|
1142
|
+
|
|
1143
|
+
for (const plugin of analyticsPlugins) {
|
|
1144
|
+
if (plugins.includes(plugin)) {
|
|
1145
|
+
indicators.push(`plugin: ${plugin}`);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
return { found: indicators.length > 0, indicators };
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// ---------------------------------------------------------------------------
|
|
1152
|
+
// Main
|
|
1153
|
+
// ---------------------------------------------------------------------------
|
|
1154
|
+
|
|
1155
|
+
function main() {
|
|
1156
|
+
const cwdArg = argv.find(a => a.startsWith('--cwd='));
|
|
1157
|
+
const cwd = cwdArg ? resolve(cwdArg.split('=')[1]) : process.cwd();
|
|
1158
|
+
|
|
1159
|
+
const ga4 = detectGA4Config();
|
|
1160
|
+
const plausible = detectPlausibleConfig();
|
|
1161
|
+
const googleApiKey = detectGoogleApiKey();
|
|
1162
|
+
const plugins = detectAnalyticsPlugins(cwd);
|
|
1163
|
+
|
|
1164
|
+
const anyConfigured = ga4.configured || plausible.configured || googleApiKey.configured || plugins.found;
|
|
1165
|
+
|
|
1166
|
+
const report = {
|
|
1167
|
+
analytics_configured: anyConfigured,
|
|
1168
|
+
ga4: ga4,
|
|
1169
|
+
plausible: plausible,
|
|
1170
|
+
google_api_key: googleApiKey,
|
|
1171
|
+
analytics_plugins: plugins,
|
|
1172
|
+
cwd,
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1175
|
+
stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
1176
|
+
exit(anyConfigured ? 0 : 1);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
main();
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
**Step 2: Commit**
|
|
1183
|
+
|
|
1184
|
+
```bash
|
|
1185
|
+
git add skills/wp-analytics/scripts/analytics_inspect.mjs
|
|
1186
|
+
git commit -m "feat(wp-analytics): add analytics detection script
|
|
1187
|
+
|
|
1188
|
+
Detects GA4, Plausible, Google API key config in WP_SITES_CONFIG.
|
|
1189
|
+
Scans for analytics plugins (Site Kit, Plausible, Matomo, etc.).
|
|
1190
|
+
JSON report to stdout, exit 0 if any config found.
|
|
1191
|
+
|
|
1192
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
---
|
|
1196
|
+
|
|
1197
|
+
### Task 7: Create wp-analytics skill (SKILL.md + 5 references)
|
|
1198
|
+
|
|
1199
|
+
**Files:**
|
|
1200
|
+
- Create: `skills/wp-analytics/SKILL.md`
|
|
1201
|
+
- Create: `skills/wp-analytics/references/ga4-integration.md`
|
|
1202
|
+
- Create: `skills/wp-analytics/references/plausible-setup.md`
|
|
1203
|
+
- Create: `skills/wp-analytics/references/cwv-monitoring.md`
|
|
1204
|
+
- Create: `skills/wp-analytics/references/analytics-dashboards.md`
|
|
1205
|
+
- Create: `skills/wp-analytics/references/traffic-attribution.md`
|
|
1206
|
+
|
|
1207
|
+
**Step 1: Write SKILL.md**
|
|
1208
|
+
|
|
1209
|
+
Follow the 10-section pattern from existing skills (frontmatter, overview, detection, prerequisites, procedures, references, recommended agent, related skills, cross-references, troubleshooting).
|
|
1210
|
+
|
|
1211
|
+
The SKILL.md should define:
|
|
1212
|
+
- **Frontmatter**: name `wp-analytics`, version 1.0.0, description, tags
|
|
1213
|
+
- **Section 1 — Overview**: Unified analytics (GA4 + Plausible + CWV) for WordPress
|
|
1214
|
+
- **Section 2 — Detection**: Run `analytics_inspect.mjs`
|
|
1215
|
+
- **Section 3 — Prerequisites**: GA4 Data API enabled, Service Account, Plausible API key, Google API key
|
|
1216
|
+
- **Section 4 — Procedures**: 6 procedures (GA4 setup, Plausible setup, CWV analysis, traffic report, performance dashboard, cross-platform comparison)
|
|
1217
|
+
- **Section 5 — MCP Tools Reference**: 14 tools table (6 GA4 + 4 PL + 4 CWV)
|
|
1218
|
+
- **Section 6 — Reference Files**: 5 files listed
|
|
1219
|
+
- **Section 7 — Recommended Agent**: wp-monitoring-agent
|
|
1220
|
+
- **Section 8 — Related Skills**: wp-search-console, wp-content-optimization, wp-content-attribution, wp-monitoring
|
|
1221
|
+
- **Section 9 — Cross-references**: Links to related skills
|
|
1222
|
+
- **Section 10 — Troubleshooting**: Common issues (quota limits, missing scopes, field data not available)
|
|
1223
|
+
|
|
1224
|
+
**Step 2: Write 5 reference files**
|
|
1225
|
+
|
|
1226
|
+
Each reference file should be 50-100 lines covering the topic from the design doc table.
|
|
1227
|
+
|
|
1228
|
+
**Step 3: Commit**
|
|
1229
|
+
|
|
1230
|
+
```bash
|
|
1231
|
+
git add skills/wp-analytics/
|
|
1232
|
+
git commit -m "feat(wp-analytics): add skill with 5 reference files
|
|
1233
|
+
|
|
1234
|
+
Unified analytics skill covering GA4, Plausible, and Core Web Vitals.
|
|
1235
|
+
14 MCP tools documented across 6 procedures.
|
|
1236
|
+
References: ga4-integration, plausible-setup, cwv-monitoring,
|
|
1237
|
+
analytics-dashboards, traffic-attribution.
|
|
1238
|
+
|
|
1239
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
1240
|
+
```
|
|
1241
|
+
|
|
1242
|
+
---
|
|
1243
|
+
|
|
1244
|
+
### Task 8: Update wp-monitoring-agent (Analytics Monitoring + CWV)
|
|
1245
|
+
|
|
1246
|
+
**Files:**
|
|
1247
|
+
- Modify: `agents/wp-monitoring-agent.md`
|
|
1248
|
+
|
|
1249
|
+
**Step 1: Add Analytics Monitoring procedure**
|
|
1250
|
+
|
|
1251
|
+
Add after "Procedure 7: Fleet Monitoring" section:
|
|
1252
|
+
|
|
1253
|
+
```markdown
|
|
1254
|
+
### Procedure 8: Analytics Monitoring (Performance Dashboard)
|
|
1255
|
+
|
|
1256
|
+
1. Fetch traffic data from GA4 (`ga4_top_pages`, `ga4_traffic_sources`) or Plausible (`pl_get_stats`, `pl_get_breakdown`)
|
|
1257
|
+
2. Fetch CWV for top pages (`cwv_batch_analyze`)
|
|
1258
|
+
3. Fetch keyword data from GSC (`gsc_top_queries`) if available
|
|
1259
|
+
4. Correlate: pages with high traffic + Poor CWV = optimization priority
|
|
1260
|
+
5. Generate Performance Dashboard Report
|
|
1261
|
+
6. If CWV Poor on top pages → recommend delegation to `wp-performance-optimizer`
|
|
1262
|
+
|
|
1263
|
+
### Procedure 9: CWV Trend Check
|
|
1264
|
+
|
|
1265
|
+
1. Run `cwv_analyze_url` on homepage and top landing pages
|
|
1266
|
+
2. Compare with CWV thresholds (Good: LCP<2.5s, INP<200ms, CLS<0.1)
|
|
1267
|
+
3. If available, fetch field data via `cwv_get_field_data` for real-user metrics
|
|
1268
|
+
4. Report status (Good/Needs Improvement/Poor) per metric
|
|
1269
|
+
5. If any metric is Poor → alert with specific pages and metrics
|
|
1270
|
+
```
|
|
1271
|
+
|
|
1272
|
+
**Step 2: Update Available Tools section**
|
|
1273
|
+
|
|
1274
|
+
Add after WP REST Bridge tools:
|
|
1275
|
+
|
|
1276
|
+
```markdown
|
|
1277
|
+
### Analytics MCP Tools (`mcp__wp-rest-bridge__ga4_*`, `mcp__wp-rest-bridge__pl_*`, `mcp__wp-rest-bridge__cwv_*`)
|
|
1278
|
+
- **GA4**: `ga4_run_report`, `ga4_get_realtime`, `ga4_top_pages`, `ga4_traffic_sources`, `ga4_user_demographics`, `ga4_conversion_events`
|
|
1279
|
+
- **Plausible**: `pl_get_stats`, `pl_get_timeseries`, `pl_get_breakdown`, `pl_get_realtime`
|
|
1280
|
+
- **CWV**: `cwv_analyze_url`, `cwv_batch_analyze`, `cwv_get_field_data`, `cwv_compare_pages`
|
|
1281
|
+
```
|
|
1282
|
+
|
|
1283
|
+
**Step 3: Update Related Skills section**
|
|
1284
|
+
|
|
1285
|
+
Add:
|
|
1286
|
+
```markdown
|
|
1287
|
+
- **`wp-analytics` skill** — analytics setup, traffic reports, CWV monitoring
|
|
1288
|
+
```
|
|
1289
|
+
|
|
1290
|
+
**Step 4: Commit**
|
|
1291
|
+
|
|
1292
|
+
```bash
|
|
1293
|
+
git add agents/wp-monitoring-agent.md
|
|
1294
|
+
git commit -m "feat(wp-monitoring-agent): add Analytics Monitoring + CWV procedures
|
|
1295
|
+
|
|
1296
|
+
- Procedure 8: Performance Dashboard (GA4/Plausible + CWV + GSC correlation)
|
|
1297
|
+
- Procedure 9: CWV Trend Check (thresholds, field data comparison)
|
|
1298
|
+
- Added 14 analytics MCP tools to Available Tools
|
|
1299
|
+
- Updated Related Skills with wp-analytics
|
|
1300
|
+
|
|
1301
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
1302
|
+
```
|
|
1303
|
+
|
|
1304
|
+
---
|
|
1305
|
+
|
|
1306
|
+
### Task 9: Update router to v14 + cross-references
|
|
1307
|
+
|
|
1308
|
+
**Files:**
|
|
1309
|
+
- Modify: `skills/wordpress-router/SKILL.md` (version bump in frontmatter)
|
|
1310
|
+
- Modify: `skills/wordpress-router/references/decision-tree.md` (v14 header + new keywords + new route)
|
|
1311
|
+
- Modify: `skills/wp-search-console/SKILL.md` (add cross-ref to wp-analytics)
|
|
1312
|
+
- Modify: `skills/wp-content-attribution/SKILL.md` (add cross-ref)
|
|
1313
|
+
- Modify: `skills/wp-content-optimization/SKILL.md` (add cross-ref)
|
|
1314
|
+
|
|
1315
|
+
**Step 1: Update decision-tree.md**
|
|
1316
|
+
|
|
1317
|
+
- Change header from v13 to v14 (add `+ analytics`)
|
|
1318
|
+
- Add to Step 0 operations keywords: `Google Analytics, GA4, traffic analytics, pageviews, sessions, user analytics, Plausible, privacy analytics, Core Web Vitals, CWV, LCP, INP, CLS, PageSpeed, page speed, site speed, performance score`
|
|
1319
|
+
- Add new route in Step 2b after the content optimization entry:
|
|
1320
|
+
```
|
|
1321
|
+
- **Google Analytics / GA4 / Plausible / traffic analytics / pageviews / sessions / user analytics / Core Web Vitals / CWV / PageSpeed / site speed / performance score**
|
|
1322
|
+
→ `wp-analytics` skill + `wp-monitoring-agent` agent
|
|
1323
|
+
```
|
|
1324
|
+
|
|
1325
|
+
**Step 2: Add cross-references in related skills**
|
|
1326
|
+
|
|
1327
|
+
- In `wp-search-console/SKILL.md` cross-references section, add: "Per correlare keyword GSC con traffico GA4, vedi `wp-analytics`"
|
|
1328
|
+
- In `wp-content-attribution/SKILL.md`: "Per correlazione contenuto→conversione completa, combina `wp-analytics` + `wp-content-attribution`"
|
|
1329
|
+
- In `wp-content-optimization/SKILL.md`: "Per prioritizzare ottimizzazione con dati CWV, combina `wp-analytics` + `wp-content-optimization`"
|
|
1330
|
+
|
|
1331
|
+
**Step 3: Commit**
|
|
1332
|
+
|
|
1333
|
+
```bash
|
|
1334
|
+
git add skills/wordpress-router/ skills/wp-search-console/SKILL.md skills/wp-content-attribution/SKILL.md skills/wp-content-optimization/SKILL.md
|
|
1335
|
+
git commit -m "feat(router): update to v14 with analytics routing
|
|
1336
|
+
|
|
1337
|
+
- Add GA4, Plausible, CWV keywords to Step 0 operations
|
|
1338
|
+
- Add wp-analytics + wp-monitoring-agent route in Step 2b
|
|
1339
|
+
- Add cross-references in wp-search-console, wp-content-attribution,
|
|
1340
|
+
wp-content-optimization
|
|
1341
|
+
|
|
1342
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
1343
|
+
```
|
|
1344
|
+
|
|
1345
|
+
---
|
|
1346
|
+
|
|
1347
|
+
### Task 10: Version bump to v2.7.0 + CHANGELOG
|
|
1348
|
+
|
|
1349
|
+
**Files:**
|
|
1350
|
+
- Modify: `package.json` (version, description)
|
|
1351
|
+
- Modify: `CHANGELOG.md` (add v2.7.0 entry)
|
|
1352
|
+
|
|
1353
|
+
**Step 1: Bump version to 2.7.0**
|
|
1354
|
+
|
|
1355
|
+
In `package.json`, change `"version": "2.6.0"` to `"version": "2.7.0"` and update description to mention analytics count.
|
|
1356
|
+
|
|
1357
|
+
**Step 2: Add CHANGELOG entry**
|
|
1358
|
+
|
|
1359
|
+
Add at top of CHANGELOG (after header), following existing format:
|
|
1360
|
+
|
|
1361
|
+
```markdown
|
|
1362
|
+
## [2.7.0] — 2026-03-XX
|
|
1363
|
+
|
|
1364
|
+
### Added — Analytics (WCOP Tier 4a)
|
|
1365
|
+
- **wp-analytics skill** — unified analytics: GA4, Plausible, Core Web Vitals
|
|
1366
|
+
- **14 new MCP tools**: 6 GA4 (`ga4_run_report`, `ga4_get_realtime`, `ga4_top_pages`, `ga4_traffic_sources`, `ga4_user_demographics`, `ga4_conversion_events`), 4 Plausible (`pl_get_stats`, `pl_get_timeseries`, `pl_get_breakdown`, `pl_get_realtime`), 4 CWV (`cwv_analyze_url`, `cwv_batch_analyze`, `cwv_get_field_data`, `cwv_compare_pages`)
|
|
1367
|
+
- **5 reference files**: ga4-integration, plausible-setup, cwv-monitoring, analytics-dashboards, traffic-attribution
|
|
1368
|
+
- **Detection script**: `analytics_inspect.mjs` — detects GA4, Plausible, Google API key config
|
|
1369
|
+
- **SiteConfig extension**: `ga4_property_id`, `ga4_service_account_key`, `plausible_api_key`, `plausible_base_url`, `google_api_key`
|
|
1370
|
+
|
|
1371
|
+
### Changed
|
|
1372
|
+
- **wp-monitoring-agent**: added Procedure 8 (Analytics Monitoring) and Procedure 9 (CWV Trend Check) with 14 analytics MCP tools
|
|
1373
|
+
- **Router v14**: added GA4, Plausible, CWV keywords and route
|
|
1374
|
+
- **Cross-references**: wp-search-console, wp-content-attribution, wp-content-optimization → wp-analytics
|
|
1375
|
+
|
|
1376
|
+
### Metrics
|
|
1377
|
+
- Skills: 37 (+1) | MCP tools: 125 (+14) | Reference files: 183 (+5) | Detection scripts: 25 (+1)
|
|
1378
|
+
```
|
|
1379
|
+
|
|
1380
|
+
**Step 3: Build final**
|
|
1381
|
+
|
|
1382
|
+
```bash
|
|
1383
|
+
cd servers/wp-rest-bridge && npx tsc
|
|
1384
|
+
```
|
|
1385
|
+
|
|
1386
|
+
**Step 4: Commit**
|
|
1387
|
+
|
|
1388
|
+
```bash
|
|
1389
|
+
git add package.json CHANGELOG.md
|
|
1390
|
+
git commit -m "chore: bump version to v2.7.0 — Analytics (GA4 + Plausible + CWV)
|
|
1391
|
+
|
|
1392
|
+
WCOP Tier 4a: 14 new MCP tools, wp-analytics skill, router v14.
|
|
1393
|
+
Skills: 37 | MCP tools: 125 | Agents: 12 | Detection scripts: 25
|
|
1394
|
+
|
|
1395
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
1396
|
+
```
|
|
1397
|
+
|
|
1398
|
+
---
|
|
1399
|
+
|
|
1400
|
+
## Release 2: v2.8.0 — Smart Alerting (Slack + Email)
|
|
1401
|
+
|
|
1402
|
+
### Task 11: Create Slack tool file (3 tools)
|
|
1403
|
+
|
|
1404
|
+
**Files:**
|
|
1405
|
+
- Modify: `servers/wp-rest-bridge/src/wordpress.ts` (SiteConfig + init + has/make)
|
|
1406
|
+
- Create: `servers/wp-rest-bridge/src/tools/slack.ts`
|
|
1407
|
+
|
|
1408
|
+
**Step 1: Add SiteConfig fields for Slack**
|
|
1409
|
+
|
|
1410
|
+
In `wordpress.ts` SiteConfig, add after `google_api_key`:
|
|
1411
|
+
|
|
1412
|
+
```typescript
|
|
1413
|
+
// Slack Integration (optional)
|
|
1414
|
+
slack_webhook_url?: string; // Incoming Webhook URL for alerts
|
|
1415
|
+
slack_bot_token?: string; // Bot Token (xoxb-...) for advanced messaging
|
|
1416
|
+
```
|
|
1417
|
+
|
|
1418
|
+
**Step 2: Add Slack client map and init**
|
|
1419
|
+
|
|
1420
|
+
Add `slackSiteClients` map. Add `initSlackClient` function (axios with Bearer token). Add init call in loop for `slack_bot_token`.
|
|
1421
|
+
|
|
1422
|
+
**Step 3: Add has/make functions**
|
|
1423
|
+
|
|
1424
|
+
```typescript
|
|
1425
|
+
export function hasSlackWebhook(siteId?: string): boolean { ... }
|
|
1426
|
+
export function hasSlackBot(siteId?: string): boolean { ... }
|
|
1427
|
+
export async function makeSlackBotRequest(method, endpoint, data, siteId): Promise<any> { ... }
|
|
1428
|
+
```
|
|
1429
|
+
|
|
1430
|
+
**Step 4: Write `slack.ts` tool file**
|
|
1431
|
+
|
|
1432
|
+
3 tools: `slack_send_alert` (POST to webhook URL), `slack_send_message` (Web API chat.postMessage), `slack_list_channels` (Web API conversations.list).
|
|
1433
|
+
|
|
1434
|
+
**Step 5: Register in index.ts**
|
|
1435
|
+
|
|
1436
|
+
Add imports and spread into allTools/toolHandlers.
|
|
1437
|
+
|
|
1438
|
+
**Step 6: Build and commit**
|
|
1439
|
+
|
|
1440
|
+
```bash
|
|
1441
|
+
cd servers/wp-rest-bridge && npx tsc
|
|
1442
|
+
git add servers/wp-rest-bridge/
|
|
1443
|
+
git commit -m "feat(wp-rest-bridge): add Slack tool file (3 tools)
|
|
1444
|
+
|
|
1445
|
+
- slack_send_alert: send via incoming webhook (zero-config)
|
|
1446
|
+
- slack_send_message: send to channel via Bot Token (Block Kit)
|
|
1447
|
+
- slack_list_channels: list workspace channels via Bot Token
|
|
1448
|
+
- SiteConfig: slack_webhook_url, slack_bot_token
|
|
1449
|
+
- Total MCP tools: 128
|
|
1450
|
+
|
|
1451
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
1452
|
+
```
|
|
1453
|
+
|
|
1454
|
+
---
|
|
1455
|
+
|
|
1456
|
+
### Task 12: Create alerting detection script
|
|
1457
|
+
|
|
1458
|
+
**Files:**
|
|
1459
|
+
- Create: `skills/wp-alerting/scripts/alerting_inspect.mjs`
|
|
1460
|
+
|
|
1461
|
+
**Step 1: Write detection script**
|
|
1462
|
+
|
|
1463
|
+
Detects: Slack webhook/bot config, SendGrid config, monitoring setup, wp-cron availability.
|
|
1464
|
+
|
|
1465
|
+
**Step 2: Commit**
|
|
1466
|
+
|
|
1467
|
+
```bash
|
|
1468
|
+
git add skills/wp-alerting/scripts/alerting_inspect.mjs
|
|
1469
|
+
git commit -m "feat(wp-alerting): add alerting detection script
|
|
1470
|
+
|
|
1471
|
+
Detects Slack webhook/bot, SendGrid, monitoring config in WP_SITES_CONFIG.
|
|
1472
|
+
|
|
1473
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
1474
|
+
```
|
|
1475
|
+
|
|
1476
|
+
---
|
|
1477
|
+
|
|
1478
|
+
### Task 13: Create wp-alerting skill (SKILL.md + 4 references)
|
|
1479
|
+
|
|
1480
|
+
**Files:**
|
|
1481
|
+
- Create: `skills/wp-alerting/SKILL.md`
|
|
1482
|
+
- Create: `skills/wp-alerting/references/slack-integration.md`
|
|
1483
|
+
- Create: `skills/wp-alerting/references/alert-thresholds.md`
|
|
1484
|
+
- Create: `skills/wp-alerting/references/escalation-paths.md`
|
|
1485
|
+
- Create: `skills/wp-alerting/references/report-scheduling.md`
|
|
1486
|
+
|
|
1487
|
+
**Step 1: Write SKILL.md + 4 reference files**
|
|
1488
|
+
|
|
1489
|
+
Alerting is procedure-based (agent-driven, not rule engine). References define thresholds, escalation paths, Slack setup, report schedules.
|
|
1490
|
+
|
|
1491
|
+
**Step 2: Commit**
|
|
1492
|
+
|
|
1493
|
+
```bash
|
|
1494
|
+
git add skills/wp-alerting/
|
|
1495
|
+
git commit -m "feat(wp-alerting): add skill with 4 reference files
|
|
1496
|
+
|
|
1497
|
+
Cross-cutting alerting: Slack + email (SendGrid), severity-based
|
|
1498
|
+
escalation, configurable thresholds, scheduled reporting.
|
|
1499
|
+
|
|
1500
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
1501
|
+
```
|
|
1502
|
+
|
|
1503
|
+
---
|
|
1504
|
+
|
|
1505
|
+
### Task 14: Update wp-monitoring-agent (Alert Dispatch)
|
|
1506
|
+
|
|
1507
|
+
**Files:**
|
|
1508
|
+
- Modify: `agents/wp-monitoring-agent.md`
|
|
1509
|
+
|
|
1510
|
+
**Step 1: Add Alert Dispatch procedure**
|
|
1511
|
+
|
|
1512
|
+
Add Procedure 10 (Alert Dispatch) with severity classification (info/warning/critical/emergency) and routing rules (Slack-only for warning, Slack+email for critical, repeat for emergency).
|
|
1513
|
+
|
|
1514
|
+
**Step 2: Add Slack tools to Available Tools**
|
|
1515
|
+
|
|
1516
|
+
**Step 3: Commit**
|
|
1517
|
+
|
|
1518
|
+
```bash
|
|
1519
|
+
git add agents/wp-monitoring-agent.md
|
|
1520
|
+
git commit -m "feat(wp-monitoring-agent): add Alert Dispatch procedure
|
|
1521
|
+
|
|
1522
|
+
- Procedure 10: severity classification + Slack/email routing
|
|
1523
|
+
- Added slack_send_alert, slack_send_message, slack_list_channels tools
|
|
1524
|
+
- Updated Related Skills with wp-alerting
|
|
1525
|
+
|
|
1526
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
1527
|
+
```
|
|
1528
|
+
|
|
1529
|
+
---
|
|
1530
|
+
|
|
1531
|
+
### Task 15: Update router to v15 + cross-references + version bump
|
|
1532
|
+
|
|
1533
|
+
**Files:**
|
|
1534
|
+
- Modify: `skills/wordpress-router/references/decision-tree.md` (v15)
|
|
1535
|
+
- Modify: `skills/wp-monitoring/SKILL.md` (cross-ref)
|
|
1536
|
+
- Modify: `skills/wp-analytics/SKILL.md` (cross-ref)
|
|
1537
|
+
- Modify: `skills/wp-search-console/SKILL.md` (cross-ref)
|
|
1538
|
+
- Modify: `package.json` (2.8.0)
|
|
1539
|
+
- Modify: `CHANGELOG.md` (v2.8.0 entry)
|
|
1540
|
+
|
|
1541
|
+
**Step 1: Update decision-tree to v15**
|
|
1542
|
+
|
|
1543
|
+
Add alerting keywords and route in Step 2b.
|
|
1544
|
+
|
|
1545
|
+
**Step 2: Add cross-references**
|
|
1546
|
+
|
|
1547
|
+
**Step 3: Bump version and CHANGELOG**
|
|
1548
|
+
|
|
1549
|
+
**Step 4: Build and commit**
|
|
1550
|
+
|
|
1551
|
+
```bash
|
|
1552
|
+
cd servers/wp-rest-bridge && npx tsc
|
|
1553
|
+
git add skills/ agents/ package.json CHANGELOG.md
|
|
1554
|
+
git commit -m "chore: bump version to v2.8.0 — Smart Alerting (Slack + Email)
|
|
1555
|
+
|
|
1556
|
+
WCOP Tier 4b: 3 new Slack MCP tools, wp-alerting skill, router v15.
|
|
1557
|
+
Skills: 38 | MCP tools: 128 | Agents: 12 | Detection scripts: 26
|
|
1558
|
+
|
|
1559
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
1560
|
+
```
|
|
1561
|
+
|
|
1562
|
+
---
|
|
1563
|
+
|
|
1564
|
+
## Release 3: v2.9.0 — Automated Workflows (Templates + Event Triggers)
|
|
1565
|
+
|
|
1566
|
+
### Task 16: Create workflows tool file (4 tools)
|
|
1567
|
+
|
|
1568
|
+
**Files:**
|
|
1569
|
+
- Create: `servers/wp-rest-bridge/src/tools/workflows.ts`
|
|
1570
|
+
|
|
1571
|
+
**Step 1: Write workflows tool file**
|
|
1572
|
+
|
|
1573
|
+
4 tools: `wf_list_triggers`, `wf_create_trigger`, `wf_update_trigger`, `wf_delete_trigger`.
|
|
1574
|
+
These use the WP REST API to read/write a mu-plugin configuration (custom REST endpoint registered by the mu-plugin).
|
|
1575
|
+
|
|
1576
|
+
**Step 2: Register in index.ts**
|
|
1577
|
+
|
|
1578
|
+
**Step 3: Build and commit**
|
|
1579
|
+
|
|
1580
|
+
```bash
|
|
1581
|
+
cd servers/wp-rest-bridge && npx tsc
|
|
1582
|
+
git add servers/wp-rest-bridge/
|
|
1583
|
+
git commit -m "feat(wp-rest-bridge): add workflows tool file (4 tools)
|
|
1584
|
+
|
|
1585
|
+
- wf_list_triggers: list configured event triggers
|
|
1586
|
+
- wf_create_trigger: register new trigger (hook → webhook)
|
|
1587
|
+
- wf_update_trigger: modify existing trigger
|
|
1588
|
+
- wf_delete_trigger: remove trigger (safety gate)
|
|
1589
|
+
- Uses WP REST API custom endpoint from mu-plugin
|
|
1590
|
+
- Total MCP tools: 132
|
|
1591
|
+
|
|
1592
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
1593
|
+
```
|
|
1594
|
+
|
|
1595
|
+
---
|
|
1596
|
+
|
|
1597
|
+
### Task 17: Add safety hook for wf_delete_trigger
|
|
1598
|
+
|
|
1599
|
+
**Files:**
|
|
1600
|
+
- Modify: `hooks/hooks.json`
|
|
1601
|
+
|
|
1602
|
+
**Step 1: Add PreToolUse hook**
|
|
1603
|
+
|
|
1604
|
+
Add new entry in the `PreToolUse` array:
|
|
1605
|
+
|
|
1606
|
+
```json
|
|
1607
|
+
{
|
|
1608
|
+
"matcher": "mcp__wp-rest-bridge__wf_delete_trigger",
|
|
1609
|
+
"hooks": [
|
|
1610
|
+
{
|
|
1611
|
+
"type": "prompt",
|
|
1612
|
+
"prompt": "The agent is about to DELETE a workflow event trigger. This will stop the automated action associated with this WordPress hook. Verify the user explicitly requested this deletion and understands which automation will stop."
|
|
1613
|
+
}
|
|
1614
|
+
]
|
|
1615
|
+
}
|
|
1616
|
+
```
|
|
1617
|
+
|
|
1618
|
+
**Step 2: Commit**
|
|
1619
|
+
|
|
1620
|
+
```bash
|
|
1621
|
+
git add hooks/hooks.json
|
|
1622
|
+
git commit -m "feat(hooks): add safety hook for wf_delete_trigger
|
|
1623
|
+
|
|
1624
|
+
PreToolUse prompt confirmation before deleting workflow event triggers.
|
|
1625
|
+
|
|
1626
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
1627
|
+
```
|
|
1628
|
+
|
|
1629
|
+
---
|
|
1630
|
+
|
|
1631
|
+
### Task 18: Create workflow detection script
|
|
1632
|
+
|
|
1633
|
+
**Files:**
|
|
1634
|
+
- Create: `skills/wp-content-workflows/scripts/workflow_inspect.mjs`
|
|
1635
|
+
|
|
1636
|
+
**Step 1: Write detection script**
|
|
1637
|
+
|
|
1638
|
+
Detects: mu-plugin `wcop-event-triggers.php`, configured triggers, wp-cron status, existing workflow-related plugins.
|
|
1639
|
+
|
|
1640
|
+
**Step 2: Commit**
|
|
1641
|
+
|
|
1642
|
+
```bash
|
|
1643
|
+
git add skills/wp-content-workflows/scripts/workflow_inspect.mjs
|
|
1644
|
+
git commit -m "feat(wp-content-workflows): add workflow detection script
|
|
1645
|
+
|
|
1646
|
+
Detects mu-plugin triggers, wp-cron, workflow plugins.
|
|
1647
|
+
|
|
1648
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
1649
|
+
```
|
|
1650
|
+
|
|
1651
|
+
---
|
|
1652
|
+
|
|
1653
|
+
### Task 19: Create wp-content-workflows skill (SKILL.md + 5 references)
|
|
1654
|
+
|
|
1655
|
+
**Files:**
|
|
1656
|
+
- Create: `skills/wp-content-workflows/SKILL.md`
|
|
1657
|
+
- Create: `skills/wp-content-workflows/references/launch-sequence.md`
|
|
1658
|
+
- Create: `skills/wp-content-workflows/references/refresh-cycle.md`
|
|
1659
|
+
- Create: `skills/wp-content-workflows/references/seasonal-campaign.md`
|
|
1660
|
+
- Create: `skills/wp-content-workflows/references/seo-sprint.md`
|
|
1661
|
+
- Create: `skills/wp-content-workflows/references/mu-plugin-triggers.md`
|
|
1662
|
+
|
|
1663
|
+
**Step 1: Write SKILL.md + 5 reference files**
|
|
1664
|
+
|
|
1665
|
+
4 workflow templates (Launch Sequence, Refresh Cycle, Seasonal Campaign, SEO Sprint) + mu-plugin architecture reference.
|
|
1666
|
+
|
|
1667
|
+
**Step 2: Commit**
|
|
1668
|
+
|
|
1669
|
+
```bash
|
|
1670
|
+
git add skills/wp-content-workflows/
|
|
1671
|
+
git commit -m "feat(wp-content-workflows): add skill with 5 reference files
|
|
1672
|
+
|
|
1673
|
+
4 workflow templates: Launch Sequence, Refresh Cycle, Seasonal Campaign,
|
|
1674
|
+
SEO Sprint. mu-plugin trigger architecture reference.
|
|
1675
|
+
|
|
1676
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
1677
|
+
```
|
|
1678
|
+
|
|
1679
|
+
---
|
|
1680
|
+
|
|
1681
|
+
### Task 20: Update wp-site-manager agent (Workflow Automation)
|
|
1682
|
+
|
|
1683
|
+
**Files:**
|
|
1684
|
+
- Modify: `agents/wp-site-manager.md`
|
|
1685
|
+
|
|
1686
|
+
**Step 1: Add Workflow Orchestration section**
|
|
1687
|
+
|
|
1688
|
+
Add delegation procedure: identify workflow → verify prerequisites → execute steps → report.
|
|
1689
|
+
|
|
1690
|
+
**Step 2: Update delegation table**
|
|
1691
|
+
|
|
1692
|
+
Add workflow-related delegation entries.
|
|
1693
|
+
|
|
1694
|
+
**Step 3: Commit**
|
|
1695
|
+
|
|
1696
|
+
```bash
|
|
1697
|
+
git add agents/wp-site-manager.md
|
|
1698
|
+
git commit -m "feat(wp-site-manager): add Workflow Automation section
|
|
1699
|
+
|
|
1700
|
+
- Workflow Orchestration procedure: identify → verify → execute → report
|
|
1701
|
+
- Updated delegation table with workflow-related entries
|
|
1702
|
+
- Added Related Skills: wp-content-workflows
|
|
1703
|
+
|
|
1704
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
1705
|
+
```
|
|
1706
|
+
|
|
1707
|
+
---
|
|
1708
|
+
|
|
1709
|
+
### Task 21: Update router to v16 + cross-references + version bump + GUIDE.md
|
|
1710
|
+
|
|
1711
|
+
**Files:**
|
|
1712
|
+
- Modify: `skills/wordpress-router/references/decision-tree.md` (v16)
|
|
1713
|
+
- Modify: `skills/wp-content/SKILL.md` (cross-ref)
|
|
1714
|
+
- Modify: `skills/wp-social-email/SKILL.md` (cross-ref)
|
|
1715
|
+
- Modify: `skills/wp-webhooks/SKILL.md` (cross-ref)
|
|
1716
|
+
- Modify: `skills/wp-content-optimization/SKILL.md` (cross-ref)
|
|
1717
|
+
- Modify: `package.json` (2.9.0)
|
|
1718
|
+
- Modify: `CHANGELOG.md` (v2.9.0 entry)
|
|
1719
|
+
- Modify: `docs/GUIDE.md` (update from v2.6.0 to v2.9.0)
|
|
1720
|
+
|
|
1721
|
+
**Step 1: Update decision-tree to v16**
|
|
1722
|
+
|
|
1723
|
+
Add workflow keywords and route in Step 2b.
|
|
1724
|
+
|
|
1725
|
+
**Step 2: Add cross-references in 4 skills**
|
|
1726
|
+
|
|
1727
|
+
**Step 3: Bump version and CHANGELOG**
|
|
1728
|
+
|
|
1729
|
+
**Step 4: Update GUIDE.md**
|
|
1730
|
+
|
|
1731
|
+
Update all counts, add v2.7.0-v2.9.0 features, add new scenarios for analytics, alerting, workflows.
|
|
1732
|
+
|
|
1733
|
+
**Step 5: Build final**
|
|
1734
|
+
|
|
1735
|
+
```bash
|
|
1736
|
+
cd servers/wp-rest-bridge && npx tsc
|
|
1737
|
+
```
|
|
1738
|
+
|
|
1739
|
+
**Step 6: Commit**
|
|
1740
|
+
|
|
1741
|
+
```bash
|
|
1742
|
+
git add skills/ agents/ hooks/ package.json CHANGELOG.md docs/GUIDE.md
|
|
1743
|
+
git commit -m "chore: bump version to v2.9.0 — Automated Workflows
|
|
1744
|
+
|
|
1745
|
+
WCOP Tier 5: 4 workflow MCP tools, wp-content-workflows skill,
|
|
1746
|
+
safety hook, router v16, GUIDE.md v2.9.0.
|
|
1747
|
+
Skills: 39 | MCP tools: 132 | Agents: 12 | Detection scripts: 27
|
|
1748
|
+
WCOP score: 8.8/10 (Observability 9, Automation 9)
|
|
1749
|
+
|
|
1750
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>"
|
|
1751
|
+
```
|
|
1752
|
+
|
|
1753
|
+
---
|
|
1754
|
+
|
|
1755
|
+
### Task 22: Update memory file
|
|
1756
|
+
|
|
1757
|
+
**Files:**
|
|
1758
|
+
- Modify: `/home/vinmor/.claude/projects/-home-vinmor/memory/wordpress-manager.md`
|
|
1759
|
+
|
|
1760
|
+
**Step 1: Update all sections**
|
|
1761
|
+
|
|
1762
|
+
- Version: 2.9.0
|
|
1763
|
+
- Skills: 39, MCP tools: 132, refs: 192, detection scripts: 27, hooks: 10
|
|
1764
|
+
- Add 3 new skill descriptions (wp-analytics, wp-alerting, wp-content-workflows)
|
|
1765
|
+
- Update router to v16
|
|
1766
|
+
- Add version history entries for v2.7.0, v2.8.0, v2.9.0
|
|
1767
|
+
|
|
1768
|
+
**Step 2: Commit memory (no git — memory file is outside repo)**
|
|
1769
|
+
|
|
1770
|
+
---
|
|
1771
|
+
|
|
1772
|
+
## Summary
|
|
1773
|
+
|
|
1774
|
+
| Release | Tasks | New Tools | New Skills | Key Files |
|
|
1775
|
+
|---------|-------|-----------|------------|-----------|
|
|
1776
|
+
| v2.7.0 | 1-10 | 14 (GA4+PL+CWV) | wp-analytics | ga4.ts, plausible.ts, cwv.ts |
|
|
1777
|
+
| v2.8.0 | 11-15 | 3 (Slack) | wp-alerting | slack.ts |
|
|
1778
|
+
| v2.9.0 | 16-22 | 4 (wf_*) | wp-content-workflows | workflows.ts |
|
|
1779
|
+
| **Total** | **22** | **21** | **3** | **4 tool files** |
|
|
1780
|
+
|
|
1781
|
+
---
|
|
1782
|
+
|
|
1783
|
+
*Implementation Plan Tier 4+5 WCOP v1.0 — wordpress-manager v2.6.0 → v2.9.0 — 2026-03-01*
|