claude-plugin-wordpress-manager 2.3.0 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/.claude-plugin/plugin.json +8 -3
  2. package/CHANGELOG.md +25 -0
  3. package/agents/wp-distribution-manager.md +98 -0
  4. package/docs/GUIDE.md +126 -10
  5. package/docs/plans/2026-03-01-tier3-wcop-design.md +373 -0
  6. package/docs/plans/2026-03-01-tier3-wcop-implementation.md +915 -0
  7. package/hooks/hooks.json +18 -0
  8. package/package.json +9 -3
  9. package/servers/wp-rest-bridge/build/tools/buffer.d.ts +3 -0
  10. package/servers/wp-rest-bridge/build/tools/buffer.js +205 -0
  11. package/servers/wp-rest-bridge/build/tools/index.js +9 -0
  12. package/servers/wp-rest-bridge/build/tools/mailchimp.d.ts +3 -0
  13. package/servers/wp-rest-bridge/build/tools/mailchimp.js +265 -0
  14. package/servers/wp-rest-bridge/build/tools/sendgrid.d.ts +3 -0
  15. package/servers/wp-rest-bridge/build/tools/sendgrid.js +255 -0
  16. package/servers/wp-rest-bridge/build/types.d.ts +122 -0
  17. package/servers/wp-rest-bridge/build/wordpress.d.ts +9 -0
  18. package/servers/wp-rest-bridge/build/wordpress.js +112 -0
  19. package/skills/wordpress-router/references/decision-tree.md +4 -2
  20. package/skills/wp-content/SKILL.md +1 -0
  21. package/skills/wp-content-repurposing/SKILL.md +1 -0
  22. package/skills/wp-social-email/SKILL.md +152 -0
  23. package/skills/wp-social-email/references/audience-segmentation.md +173 -0
  24. package/skills/wp-social-email/references/buffer-social-publishing.md +124 -0
  25. package/skills/wp-social-email/references/content-to-distribution.md +156 -0
  26. package/skills/wp-social-email/references/distribution-analytics.md +208 -0
  27. package/skills/wp-social-email/references/mailchimp-integration.md +145 -0
  28. package/skills/wp-social-email/references/sendgrid-transactional.md +165 -0
  29. package/skills/wp-social-email/scripts/distribution_inspect.mjs +165 -0
  30. package/skills/wp-webhooks/SKILL.md +1 -0
@@ -0,0 +1,255 @@
1
+ import { makeSendGridRequest, hasSendGrid } from '../wordpress.js';
2
+ import { z } from 'zod';
3
+ // ── Zod Schemas ─────────────────────────────────────────────────
4
+ const sgSendEmailSchema = z.object({
5
+ to_email: z.string().describe('Recipient email address'),
6
+ to_name: z.string().optional().describe('Recipient name'),
7
+ from_email: z.string().describe('Sender email address'),
8
+ from_name: z.string().optional().describe('Sender name'),
9
+ subject: z.string().describe('Email subject line'),
10
+ content_type: z.string().optional().default('text/html')
11
+ .describe('Content MIME type (default: text/html)'),
12
+ content_value: z.string().describe('Email body content'),
13
+ template_id: z.string().optional()
14
+ .describe('SendGrid dynamic template ID (overrides content)'),
15
+ }).strict();
16
+ const sgListTemplatesSchema = z.object({
17
+ generations: z.enum(['legacy', 'dynamic']).optional()
18
+ .describe('Filter by template generation type'),
19
+ page_size: z.number().optional()
20
+ .describe('Number of templates per page'),
21
+ }).strict();
22
+ const sgGetTemplateSchema = z.object({
23
+ template_id: z.string().describe('SendGrid template ID'),
24
+ }).strict();
25
+ const sgListContactsSchema = z.object({
26
+ query: z.string().optional()
27
+ .describe('SGQL query string for filtering contacts'),
28
+ }).strict();
29
+ const sgAddContactsSchema = z.object({
30
+ contacts: z.array(z.object({
31
+ email: z.string().describe('Contact email address'),
32
+ first_name: z.string().optional().describe('Contact first name'),
33
+ last_name: z.string().optional().describe('Contact last name'),
34
+ custom_fields: z.record(z.any()).optional().describe('Custom field key-value pairs'),
35
+ })).describe('Array of contacts to add or update'),
36
+ }).strict();
37
+ const sgGetStatsSchema = z.object({
38
+ start_date: z.string().describe('Start date in YYYY-MM-DD format'),
39
+ end_date: z.string().optional().describe('End date in YYYY-MM-DD format'),
40
+ aggregated_by: z.enum(['day', 'week', 'month']).optional()
41
+ .describe('Aggregation period for stats'),
42
+ }).strict();
43
+ // ── Tool Definitions ────────────────────────────────────────────
44
+ export const sendgridTools = [
45
+ {
46
+ name: "sg_send_email",
47
+ description: "Sends a transactional email via SendGrid (DESTRUCTIVE — sends real email)",
48
+ inputSchema: {
49
+ type: "object",
50
+ properties: {
51
+ to_email: { type: "string", description: "Recipient email address" },
52
+ to_name: { type: "string", description: "Recipient name" },
53
+ from_email: { type: "string", description: "Sender email address" },
54
+ from_name: { type: "string", description: "Sender name" },
55
+ subject: { type: "string", description: "Email subject line" },
56
+ content_type: { type: "string", description: "Content MIME type (default: text/html)" },
57
+ content_value: { type: "string", description: "Email body content" },
58
+ template_id: { type: "string", description: "SendGrid dynamic template ID (overrides content)" },
59
+ },
60
+ required: ["to_email", "from_email", "subject", "content_value"],
61
+ },
62
+ },
63
+ {
64
+ name: "sg_list_templates",
65
+ description: "Lists all SendGrid email templates",
66
+ inputSchema: {
67
+ type: "object",
68
+ properties: {
69
+ generations: {
70
+ type: "string",
71
+ enum: ["legacy", "dynamic"],
72
+ description: "Filter by template generation type",
73
+ },
74
+ page_size: { type: "number", description: "Number of templates per page" },
75
+ },
76
+ },
77
+ },
78
+ {
79
+ name: "sg_get_template",
80
+ description: "Gets a specific SendGrid template with its versions",
81
+ inputSchema: {
82
+ type: "object",
83
+ properties: {
84
+ template_id: { type: "string", description: "SendGrid template ID" },
85
+ },
86
+ required: ["template_id"],
87
+ },
88
+ },
89
+ {
90
+ name: "sg_list_contacts",
91
+ description: "Lists SendGrid Marketing contacts with optional search",
92
+ inputSchema: {
93
+ type: "object",
94
+ properties: {
95
+ query: { type: "string", description: "SGQL query string for filtering contacts" },
96
+ },
97
+ },
98
+ },
99
+ {
100
+ name: "sg_add_contacts",
101
+ description: "Adds or updates contacts in SendGrid Marketing",
102
+ inputSchema: {
103
+ type: "object",
104
+ properties: {
105
+ contacts: {
106
+ type: "array",
107
+ items: {
108
+ type: "object",
109
+ properties: {
110
+ email: { type: "string", description: "Contact email address" },
111
+ first_name: { type: "string", description: "Contact first name" },
112
+ last_name: { type: "string", description: "Contact last name" },
113
+ custom_fields: { type: "object", description: "Custom field key-value pairs" },
114
+ },
115
+ required: ["email"],
116
+ },
117
+ description: "Array of contacts to add or update",
118
+ },
119
+ },
120
+ required: ["contacts"],
121
+ },
122
+ },
123
+ {
124
+ name: "sg_get_stats",
125
+ description: "Gets email delivery statistics for a date range",
126
+ inputSchema: {
127
+ type: "object",
128
+ properties: {
129
+ start_date: { type: "string", description: "Start date in YYYY-MM-DD format" },
130
+ end_date: { type: "string", description: "End date in YYYY-MM-DD format" },
131
+ aggregated_by: {
132
+ type: "string",
133
+ enum: ["day", "week", "month"],
134
+ description: "Aggregation period for stats",
135
+ },
136
+ },
137
+ required: ["start_date"],
138
+ },
139
+ },
140
+ ];
141
+ // ── Handlers ────────────────────────────────────────────────────
142
+ export const sendgridHandlers = {
143
+ sg_send_email: async (params) => {
144
+ if (!hasSendGrid()) {
145
+ return { toolResult: { isError: true, content: [{ type: "text", text: "SendGrid not configured. Add sendgrid_api_key to WP_SITES_CONFIG." }] } };
146
+ }
147
+ try {
148
+ const { to_email, to_name, from_email, from_name, subject, content_type, content_value, template_id } = params;
149
+ const body = {
150
+ personalizations: [{ to: [{ email: to_email, ...(to_name ? { name: to_name } : {}) }], subject }],
151
+ from: { email: from_email, ...(from_name ? { name: from_name } : {}) },
152
+ subject,
153
+ content: [{ type: content_type || 'text/html', value: content_value }],
154
+ };
155
+ if (template_id)
156
+ body.template_id = template_id;
157
+ const response = await makeSendGridRequest("POST", "mail/send", body);
158
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify(response ?? { success: true, message: "Email sent successfully" }, null, 2) }] } };
159
+ }
160
+ catch (error) {
161
+ const errorMessage = error.response?.data?.errors?.[0]?.message || error.message;
162
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error sending email: ${errorMessage}` }] } };
163
+ }
164
+ },
165
+ sg_list_templates: async (params) => {
166
+ if (!hasSendGrid()) {
167
+ return { toolResult: { isError: true, content: [{ type: "text", text: "SendGrid not configured. Add sendgrid_api_key to WP_SITES_CONFIG." }] } };
168
+ }
169
+ try {
170
+ const { generations, page_size } = params;
171
+ const query = {};
172
+ if (generations)
173
+ query.generations = generations;
174
+ if (page_size !== undefined)
175
+ query.page_size = page_size;
176
+ const qs = Object.entries(query).map(([k, v]) => `${k}=${v}`).join('&');
177
+ const endpoint = `templates${qs ? '?' + qs : ''}`;
178
+ const response = await makeSendGridRequest("GET", endpoint);
179
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] } };
180
+ }
181
+ catch (error) {
182
+ const errorMessage = error.response?.data?.errors?.[0]?.message || error.message;
183
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error listing templates: ${errorMessage}` }] } };
184
+ }
185
+ },
186
+ sg_get_template: async (params) => {
187
+ if (!hasSendGrid()) {
188
+ return { toolResult: { isError: true, content: [{ type: "text", text: "SendGrid not configured. Add sendgrid_api_key to WP_SITES_CONFIG." }] } };
189
+ }
190
+ try {
191
+ const { template_id } = params;
192
+ const response = await makeSendGridRequest("GET", `templates/${template_id}`);
193
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] } };
194
+ }
195
+ catch (error) {
196
+ const errorMessage = error.response?.data?.errors?.[0]?.message || error.message;
197
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting template: ${errorMessage}` }] } };
198
+ }
199
+ },
200
+ sg_list_contacts: async (params) => {
201
+ if (!hasSendGrid()) {
202
+ return { toolResult: { isError: true, content: [{ type: "text", text: "SendGrid not configured. Add sendgrid_api_key to WP_SITES_CONFIG." }] } };
203
+ }
204
+ try {
205
+ const { query } = params;
206
+ let response;
207
+ if (query) {
208
+ response = await makeSendGridRequest("POST", "marketing/contacts/search", { query });
209
+ }
210
+ else {
211
+ response = await makeSendGridRequest("GET", "marketing/contacts");
212
+ }
213
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] } };
214
+ }
215
+ catch (error) {
216
+ const errorMessage = error.response?.data?.errors?.[0]?.message || error.message;
217
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error listing contacts: ${errorMessage}` }] } };
218
+ }
219
+ },
220
+ sg_add_contacts: async (params) => {
221
+ if (!hasSendGrid()) {
222
+ return { toolResult: { isError: true, content: [{ type: "text", text: "SendGrid not configured. Add sendgrid_api_key to WP_SITES_CONFIG." }] } };
223
+ }
224
+ try {
225
+ const { contacts } = params;
226
+ const response = await makeSendGridRequest("PUT", "marketing/contacts", { contacts });
227
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] } };
228
+ }
229
+ catch (error) {
230
+ const errorMessage = error.response?.data?.errors?.[0]?.message || error.message;
231
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error adding contacts: ${errorMessage}` }] } };
232
+ }
233
+ },
234
+ sg_get_stats: async (params) => {
235
+ if (!hasSendGrid()) {
236
+ return { toolResult: { isError: true, content: [{ type: "text", text: "SendGrid not configured. Add sendgrid_api_key to WP_SITES_CONFIG." }] } };
237
+ }
238
+ try {
239
+ const { start_date, end_date, aggregated_by } = params;
240
+ const query = { start_date };
241
+ if (end_date)
242
+ query.end_date = end_date;
243
+ if (aggregated_by)
244
+ query.aggregated_by = aggregated_by;
245
+ const qs = Object.entries(query).map(([k, v]) => `${k}=${v}`).join('&');
246
+ const endpoint = `stats?${qs}`;
247
+ const response = await makeSendGridRequest("GET", endpoint);
248
+ return { toolResult: { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] } };
249
+ }
250
+ catch (error) {
251
+ const errorMessage = error.response?.data?.errors?.[0]?.message || error.message;
252
+ return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting stats: ${errorMessage}` }] } };
253
+ }
254
+ },
255
+ };
@@ -258,4 +258,126 @@ export interface WPNetworkSite {
258
258
  spam: boolean;
259
259
  deleted: boolean;
260
260
  }
261
+ export interface MCMailchimpAudience {
262
+ id: string;
263
+ name: string;
264
+ member_count: number;
265
+ campaign_defaults: {
266
+ from_name: string;
267
+ from_email: string;
268
+ subject: string;
269
+ };
270
+ stats: {
271
+ member_count: number;
272
+ unsubscribe_count: number;
273
+ open_rate: number;
274
+ click_rate: number;
275
+ };
276
+ date_created: string;
277
+ }
278
+ export interface MCCampaign {
279
+ id: string;
280
+ type: string;
281
+ status: string;
282
+ emails_sent: number;
283
+ send_time: string;
284
+ settings: {
285
+ subject_line: string;
286
+ from_name: string;
287
+ reply_to: string;
288
+ };
289
+ report_summary?: {
290
+ opens: number;
291
+ unique_opens: number;
292
+ clicks: number;
293
+ subscriber_clicks: number;
294
+ };
295
+ }
296
+ export interface MCCampaignReport {
297
+ id: string;
298
+ campaign_title: string;
299
+ emails_sent: number;
300
+ opens: {
301
+ opens_total: number;
302
+ unique_opens: number;
303
+ open_rate: number;
304
+ };
305
+ clicks: {
306
+ clicks_total: number;
307
+ unique_clicks: number;
308
+ click_rate: number;
309
+ };
310
+ unsubscribed: number;
311
+ bounces: {
312
+ hard_bounces: number;
313
+ soft_bounces: number;
314
+ };
315
+ }
316
+ export interface BufProfile {
317
+ id: string;
318
+ service: string;
319
+ formatted_username: string;
320
+ avatar: string;
321
+ counts: {
322
+ sent: number;
323
+ pending: number;
324
+ };
325
+ }
326
+ export interface BufUpdate {
327
+ id: string;
328
+ text: string;
329
+ profile_id: string;
330
+ status: string;
331
+ sent_at?: number;
332
+ due_at?: number;
333
+ statistics?: {
334
+ clicks: number;
335
+ reach: number;
336
+ impressions: number;
337
+ };
338
+ }
339
+ export interface SGEmailRequest {
340
+ personalizations: {
341
+ to: {
342
+ email: string;
343
+ name?: string;
344
+ }[];
345
+ subject?: string;
346
+ }[];
347
+ from: {
348
+ email: string;
349
+ name?: string;
350
+ };
351
+ subject: string;
352
+ content: {
353
+ type: string;
354
+ value: string;
355
+ }[];
356
+ template_id?: string;
357
+ }
358
+ export interface SGTemplate {
359
+ id: string;
360
+ name: string;
361
+ generation: string;
362
+ updated_at: string;
363
+ versions: {
364
+ id: string;
365
+ name: string;
366
+ active: number;
367
+ subject: string;
368
+ }[];
369
+ }
370
+ export interface SGStats {
371
+ date: string;
372
+ stats: {
373
+ metrics: {
374
+ requests: number;
375
+ delivered: number;
376
+ opens: number;
377
+ clicks: number;
378
+ bounces: number;
379
+ spam_reports: number;
380
+ };
381
+ }[];
382
+ }
261
383
  export {};
@@ -11,6 +11,9 @@ interface SiteConfig {
11
11
  ssh_key?: string;
12
12
  ssh_port?: number;
13
13
  is_multisite?: boolean;
14
+ mailchimp_api_key?: string;
15
+ buffer_access_token?: string;
16
+ sendgrid_api_key?: string;
14
17
  }
15
18
  /**
16
19
  * Parse WP_SITES_CONFIG JSON and initialize all site clients
@@ -69,6 +72,12 @@ export declare function hasWooCommerce(siteId?: string): boolean;
69
72
  * Uses Consumer Key/Secret auth and wc/v3 namespace by default.
70
73
  */
71
74
  export declare function makeWooCommerceRequest(method: string, endpoint: string, data?: any, options?: WordPressRequestOptions): Promise<any>;
75
+ export declare function hasMailchimp(siteId?: string): boolean;
76
+ export declare function makeMailchimpRequest(method: string, endpoint: string, data?: any, siteId?: string): Promise<any>;
77
+ export declare function hasBuffer(siteId?: string): boolean;
78
+ export declare function makeBufferRequest(method: string, endpoint: string, data?: any, siteId?: string): Promise<any>;
79
+ export declare function hasSendGrid(siteId?: string): boolean;
80
+ export declare function makeSendGridRequest(method: string, endpoint: string, data?: any, siteId?: string): Promise<any>;
72
81
  /**
73
82
  * Search the WordPress.org Plugin Repository
74
83
  */
@@ -32,6 +32,9 @@ class ConcurrencyLimiter {
32
32
  const siteClients = new Map();
33
33
  const siteLimiters = new Map();
34
34
  const wcSiteClients = new Map();
35
+ const mcSiteClients = new Map();
36
+ const bufSiteClients = new Map();
37
+ const sgSiteClients = new Map();
35
38
  let activeSiteId = '';
36
39
  const parsedSiteConfigs = new Map();
37
40
  const MAX_CONCURRENT_PER_SITE = 5;
@@ -84,6 +87,18 @@ export async function initWordPress() {
84
87
  await initWcClient(site.id, site.url, site.wc_consumer_key, site.wc_consumer_secret);
85
88
  logToStderr(`Initialized WooCommerce for site: ${site.id}`);
86
89
  }
90
+ if (site.mailchimp_api_key) {
91
+ await initMailchimpClient(site.id, site.mailchimp_api_key);
92
+ logToStderr(`Initialized Mailchimp for site: ${site.id}`);
93
+ }
94
+ if (site.buffer_access_token) {
95
+ await initBufferClient(site.id, site.buffer_access_token);
96
+ logToStderr(`Initialized Buffer for site: ${site.id}`);
97
+ }
98
+ if (site.sendgrid_api_key) {
99
+ await initSendGridClient(site.id, site.sendgrid_api_key);
100
+ logToStderr(`Initialized SendGrid for site: ${site.id}`);
101
+ }
87
102
  }
88
103
  activeSiteId = defaultSite || sites[0].id;
89
104
  logToStderr(`Active site: ${activeSiteId}`);
@@ -152,6 +167,40 @@ async function initWcClient(id, url, consumerKey, consumerSecret) {
152
167
  }
153
168
  wcSiteClients.set(id, client);
154
169
  }
170
+ async function initMailchimpClient(id, apiKey) {
171
+ const dc = apiKey.split('-').pop() || 'us21';
172
+ const client = axios.create({
173
+ baseURL: `https://${dc}.api.mailchimp.com/3.0/`,
174
+ headers: {
175
+ 'Content-Type': 'application/json',
176
+ 'Authorization': `Basic ${Buffer.from(`anystring:${apiKey}`).toString('base64')}`,
177
+ },
178
+ timeout: DEFAULT_TIMEOUT_MS,
179
+ });
180
+ mcSiteClients.set(id, client);
181
+ }
182
+ async function initBufferClient(id, accessToken) {
183
+ const client = axios.create({
184
+ baseURL: 'https://api.bufferapp.com/1/',
185
+ headers: {
186
+ 'Content-Type': 'application/json',
187
+ },
188
+ params: { access_token: accessToken },
189
+ timeout: DEFAULT_TIMEOUT_MS,
190
+ });
191
+ bufSiteClients.set(id, client);
192
+ }
193
+ async function initSendGridClient(id, apiKey) {
194
+ const client = axios.create({
195
+ baseURL: 'https://api.sendgrid.com/v3/',
196
+ headers: {
197
+ 'Content-Type': 'application/json',
198
+ 'Authorization': `Bearer ${apiKey}`,
199
+ },
200
+ timeout: DEFAULT_TIMEOUT_MS,
201
+ });
202
+ sgSiteClients.set(id, client);
203
+ }
155
204
  // ── Site Management ──────────────────────────────────────────────────
156
205
  /**
157
206
  * Get the active site's client, or a specific site's client
@@ -357,6 +406,69 @@ export async function makeWooCommerceRequest(method, endpoint, data, options) {
357
406
  limiter.release();
358
407
  }
359
408
  }
409
+ // ── Mailchimp Request Interface ──────────────────────────────────
410
+ export function hasMailchimp(siteId) {
411
+ const id = siteId || activeSiteId;
412
+ return mcSiteClients.has(id);
413
+ }
414
+ export async function makeMailchimpRequest(method, endpoint, data, siteId) {
415
+ const id = siteId || activeSiteId;
416
+ const client = mcSiteClients.get(id);
417
+ if (!client) {
418
+ throw new Error(`Mailchimp not configured for site "${id}". Add mailchimp_api_key to WP_SITES_CONFIG.`);
419
+ }
420
+ const limiter = getLimiter(id);
421
+ await limiter.acquire();
422
+ try {
423
+ const response = await client.request({ method, url: endpoint, data: method !== 'GET' ? data : undefined, params: method === 'GET' ? data : undefined });
424
+ return response.data;
425
+ }
426
+ finally {
427
+ limiter.release();
428
+ }
429
+ }
430
+ // ── Buffer Request Interface ─────────────────────────────────────
431
+ export function hasBuffer(siteId) {
432
+ const id = siteId || activeSiteId;
433
+ return bufSiteClients.has(id);
434
+ }
435
+ export async function makeBufferRequest(method, endpoint, data, siteId) {
436
+ const id = siteId || activeSiteId;
437
+ const client = bufSiteClients.get(id);
438
+ if (!client) {
439
+ throw new Error(`Buffer not configured for site "${id}". Add buffer_access_token to WP_SITES_CONFIG.`);
440
+ }
441
+ const limiter = getLimiter(id);
442
+ await limiter.acquire();
443
+ try {
444
+ const response = await client.request({ method, url: endpoint, data: method !== 'GET' ? data : undefined, params: method === 'GET' ? data : undefined });
445
+ return response.data;
446
+ }
447
+ finally {
448
+ limiter.release();
449
+ }
450
+ }
451
+ // ── SendGrid Request Interface ───────────────────────────────────
452
+ export function hasSendGrid(siteId) {
453
+ const id = siteId || activeSiteId;
454
+ return sgSiteClients.has(id);
455
+ }
456
+ export async function makeSendGridRequest(method, endpoint, data, siteId) {
457
+ const id = siteId || activeSiteId;
458
+ const client = sgSiteClients.get(id);
459
+ if (!client) {
460
+ throw new Error(`SendGrid not configured for site "${id}". Add sendgrid_api_key to WP_SITES_CONFIG.`);
461
+ }
462
+ const limiter = getLimiter(id);
463
+ await limiter.acquire();
464
+ try {
465
+ const response = await client.request({ method, url: endpoint, data: method !== 'GET' ? data : undefined, params: method === 'GET' ? data : undefined });
466
+ return response.data;
467
+ }
468
+ finally {
469
+ limiter.release();
470
+ }
471
+ }
360
472
  // ── Plugin Repository (External API) ────────────────────────────────
361
473
  /**
362
474
  * Search the WordPress.org Plugin Repository
@@ -1,4 +1,4 @@
1
- # Router decision tree (v10 — development + local environment + operations + multisite + CI/CD + monitoring + webhooks + content repurposing + programmatic SEO + content attribution + multi-language network)
1
+ # Router decision tree (v11 — development + local environment + operations + multisite + CI/CD + monitoring + webhooks + content repurposing + programmatic SEO + content attribution + multi-language network + social/email distribution)
2
2
 
3
3
  This routing guide covers WordPress **development**, **local environment**, and **operations** workflows.
4
4
 
@@ -14,7 +14,7 @@ Keywords that indicate **local environment**:
14
14
  local site, Studio, LocalWP, Local by Flywheel, wp-env, local WordPress, start site, stop site, create local site, local development, symlink plugin, local database, switch PHP version, localhost, local preview, detect environment, WASM, SQLite local
15
15
 
16
16
  Keywords that indicate **operations**:
17
- deploy, push to production, audit, security check, backup, restore, migrate, move site, create post, manage content, site status, check plugins, performance check, SEO audit, WooCommerce, prodotto, ordine, coupon, negozio, catalogo, inventario, vendite, carrello, multisite, network, sub-site, sub-sito, domain mapping, super admin, network activate, monitor, uptime, health report, trend, scansione periodica, alerting, performance baseline, fleet, all sites, network health, cross-site, webhook, outbound notification, event propagation, Zapier, content sync, repurpose content, social posts from blog, content atomization, newsletter from posts, content distribution, programmatic SEO, template pages, city pages, location pages, bulk page generation, scalable landing pages, content ROI, attribution, which content drives sales, conversion tracking, UTM tracking, revenue per post, multilingual, multi-language, hreflang, international SEO, translate site, language sites, localize content
17
+ deploy, push to production, audit, security check, backup, restore, migrate, move site, create post, manage content, site status, check plugins, performance check, SEO audit, WooCommerce, prodotto, ordine, coupon, negozio, catalogo, inventario, vendite, carrello, multisite, network, sub-site, sub-sito, domain mapping, super admin, network activate, monitor, uptime, health report, trend, scansione periodica, alerting, performance baseline, fleet, all sites, network health, cross-site, webhook, outbound notification, event propagation, Zapier, content sync, repurpose content, social posts from blog, content atomization, newsletter from posts, content distribution, programmatic SEO, template pages, city pages, location pages, bulk page generation, scalable landing pages, content ROI, attribution, which content drives sales, conversion tracking, UTM tracking, revenue per post, multilingual, multi-language, hreflang, international SEO, translate site, language sites, localize content, social publish, schedule post, Buffer, email campaign, Mailchimp, SendGrid, transactional email, content distribution, newsletter send
18
18
 
19
19
  Keywords that indicate **development**:
20
20
  create block, block.json, theme.json, register_rest_route, plugin development, hooks, PHPStan, build, test, scaffold, i18n, translation, accessibility, a11y, headless, decoupled, WPGraphQL, CI, CD, pipeline, GitHub Actions, GitLab CI, deploy automatico, workflow, quality gate
@@ -104,6 +104,8 @@ Priority: `gutenberg` > `wp-core` > `wp-site` > `wp-block-theme` > `wp-block-plu
104
104
  → `wp-content-attribution` skill + `wp-ecommerce-manager` agent
105
105
  - **Multi-language / multilingual / hreflang / international SEO / language sites / translate network**
106
106
  → `wp-multilang-network` skill + `wp-site-manager` agent
107
+ - **Social/email distribution / publish to social / schedule post / email campaign / Mailchimp / Buffer / SendGrid / newsletter / transactional email / content distribution**
108
+ → `wp-social-email` skill + `wp-distribution-manager` agent
107
109
 
108
110
  ## Step 2c: route by local environment intent (keywords)
109
111
 
@@ -105,3 +105,4 @@ Tool: assign_terms_to_content — assign terms to content
105
105
  ### Related Skills
106
106
  - **`wp-content-repurposing`** — transform existing content for social media, email, and multi-channel distribution
107
107
  - **`wp-content-attribution`** — measure which content drives WooCommerce sales (UTM tracking, attribution models, ROI)
108
+ - **wp-social-email** — distribute content to social media and email after creation
@@ -94,3 +94,4 @@ See `references/platform-specs.md`
94
94
  - **`wp-content`** — content creation and lifecycle management (source content)
95
95
  - **`wp-headless`** — headless content delivery for multi-channel architectures
96
96
  - **`wp-woocommerce`** — product content for e-commerce repurposing
97
+ - **wp-social-email** — publish repurposed content to social and email channels