claude-plugin-wordpress-manager 2.9.0 → 2.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -0
- package/agents/wp-content-strategist.md +58 -1
- package/agents/wp-distribution-manager.md +39 -6
- package/docs/GUIDE.md +145 -14
- package/docs/plans/2026-03-01-tier6-7-design.md +246 -0
- package/docs/plans/2026-03-01-tier6-7-implementation.md +1629 -0
- package/hooks/hooks.json +18 -0
- package/package.json +6 -3
- package/servers/wp-rest-bridge/build/tools/index.js +9 -0
- package/servers/wp-rest-bridge/build/tools/linkedin.js +203 -0
- package/servers/wp-rest-bridge/build/tools/schema.js +159 -0
- package/servers/wp-rest-bridge/build/tools/twitter.js +183 -0
- package/servers/wp-rest-bridge/build/wordpress.js +94 -0
- package/skills/wordpress-router/references/decision-tree.md +10 -2
- package/skills/wp-content-generation/SKILL.md +128 -0
- package/skills/wp-content-generation/references/brief-templates.md +151 -0
- package/skills/wp-content-generation/references/generation-workflow.md +132 -0
- package/skills/wp-content-generation/references/outline-patterns.md +188 -0
- package/skills/wp-content-generation/scripts/content_gen_inspect.mjs +90 -0
- package/skills/wp-content-repurposing/SKILL.md +13 -0
- package/skills/wp-content-repurposing/references/auto-transform-pipeline.md +128 -0
- package/skills/wp-content-repurposing/references/transform-templates.md +304 -0
- package/skills/wp-linkedin/SKILL.md +96 -0
- package/skills/wp-linkedin/references/linkedin-analytics.md +58 -0
- package/skills/wp-linkedin/references/linkedin-posting.md +53 -0
- package/skills/wp-linkedin/references/linkedin-setup.md +59 -0
- package/skills/wp-linkedin/scripts/linkedin_inspect.mjs +55 -0
- package/skills/wp-structured-data/SKILL.md +94 -0
- package/skills/wp-structured-data/references/injection-patterns.md +160 -0
- package/skills/wp-structured-data/references/schema-types.md +127 -0
- package/skills/wp-structured-data/references/validation-guide.md +89 -0
- package/skills/wp-structured-data/scripts/schema_inspect.mjs +88 -0
- package/skills/wp-twitter/SKILL.md +101 -0
- package/skills/wp-twitter/references/twitter-analytics.md +60 -0
- package/skills/wp-twitter/references/twitter-posting.md +66 -0
- package/skills/wp-twitter/references/twitter-setup.md +62 -0
- package/skills/wp-twitter/scripts/twitter_inspect.mjs +58 -0
|
@@ -0,0 +1,1629 @@
|
|
|
1
|
+
# Tier 6+7 Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Complete Distribution layer (8→9/10) with LinkedIn + Twitter/X direct APIs and auto-transform pipeline, then complete Content Factory layer (9→10/10) with AI content generation procedures and structured data MCP tools.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Connector-per-Platform approach. Each social API gets its own TypeScript tool file (`linkedin.js`, `twitter.js`) following the exact pattern of `buffer.js`/`slack.js`. New helper functions in `wordpress.js` for client init + has/make helpers. Structured data tools in `schema.js` using WordPress REST API (no external auth). Content generation is purely procedure-based (skill references only, no MCP tools). Three incremental releases: v2.10.0 (social), v2.11.0 (auto-transform), v2.12.0 (content gen + schema).
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** JavaScript (compiled from TypeScript), axios, zod, MCP SDK, Node.js ESM modules.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Release v2.10.0 — Direct Social APIs (LinkedIn + Twitter/X)
|
|
14
|
+
|
|
15
|
+
### Task 1: Add LinkedIn client helpers to wordpress.js
|
|
16
|
+
|
|
17
|
+
**Files:**
|
|
18
|
+
- Modify: `servers/wp-rest-bridge/build/wordpress.js`
|
|
19
|
+
|
|
20
|
+
**Context:** Every external service follows the same pattern in `wordpress.js`:
|
|
21
|
+
1. A `Map` for per-site axios clients (line ~34-41)
|
|
22
|
+
2. An `initXxxClient()` function that creates an axios instance
|
|
23
|
+
3. A `hasXxx()` function to check if configured
|
|
24
|
+
4. A `makeXxxRequest()` function for API calls
|
|
25
|
+
5. Registration in the `initWordPress()` loop (line ~83-114)
|
|
26
|
+
|
|
27
|
+
LinkedIn uses OAuth 2.0. The Community Management API v2 base URL is `https://api.linkedin.com/rest/`. Auth header: `Authorization: Bearer {token}`. Required header: `LinkedIn-Version: 202401`.
|
|
28
|
+
|
|
29
|
+
**Step 1: Add LinkedIn client Map and init function**
|
|
30
|
+
|
|
31
|
+
Add after `const slackBotClients = new Map();` (line 41):
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
const liSiteClients = new Map();
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Add after the `initSlackBotClient` function (after line 236):
|
|
38
|
+
|
|
39
|
+
```javascript
|
|
40
|
+
async function initLinkedInClient(id, accessToken) {
|
|
41
|
+
const client = axios.create({
|
|
42
|
+
baseURL: 'https://api.linkedin.com/rest/',
|
|
43
|
+
headers: {
|
|
44
|
+
'Content-Type': 'application/json',
|
|
45
|
+
'Authorization': `Bearer ${accessToken}`,
|
|
46
|
+
'LinkedIn-Version': '202401',
|
|
47
|
+
'X-Restli-Protocol-Version': '2.0.0',
|
|
48
|
+
},
|
|
49
|
+
timeout: DEFAULT_TIMEOUT_MS,
|
|
50
|
+
});
|
|
51
|
+
liSiteClients.set(id, client);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Step 2: Add LinkedIn has/make/get helpers**
|
|
56
|
+
|
|
57
|
+
Add after the `makeSlackBotRequest` function (after line ~649):
|
|
58
|
+
|
|
59
|
+
```javascript
|
|
60
|
+
// ── LinkedIn Interface ──────────────────────────────────────────
|
|
61
|
+
export function hasLinkedIn(siteId) {
|
|
62
|
+
const id = siteId || activeSiteId;
|
|
63
|
+
return liSiteClients.has(id);
|
|
64
|
+
}
|
|
65
|
+
export async function makeLinkedInRequest(method, endpoint, data, siteId) {
|
|
66
|
+
const id = siteId || activeSiteId;
|
|
67
|
+
const client = liSiteClients.get(id);
|
|
68
|
+
if (!client) {
|
|
69
|
+
throw new Error(`LinkedIn not configured for site "${id}". Add linkedin_access_token to WP_SITES_CONFIG.`);
|
|
70
|
+
}
|
|
71
|
+
const limiter = getLimiter(id);
|
|
72
|
+
await limiter.acquire();
|
|
73
|
+
try {
|
|
74
|
+
const response = await client.request({ method, url: endpoint, data: method !== 'GET' ? data : undefined, params: method === 'GET' ? data : undefined });
|
|
75
|
+
return response.data;
|
|
76
|
+
} finally {
|
|
77
|
+
limiter.release();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
export function getLinkedInPersonUrn(siteId) {
|
|
81
|
+
const id = siteId || activeSiteId;
|
|
82
|
+
const sites = JSON.parse(process.env.WP_SITES_CONFIG || '[]');
|
|
83
|
+
const site = sites.find((s) => s.id === id);
|
|
84
|
+
if (!site?.linkedin_person_urn) {
|
|
85
|
+
throw new Error(`LinkedIn person URN not configured for site "${id}". Add linkedin_person_urn to WP_SITES_CONFIG.`);
|
|
86
|
+
}
|
|
87
|
+
return site.linkedin_person_urn;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Step 3: Register LinkedIn in initWordPress() loop**
|
|
92
|
+
|
|
93
|
+
Add after the `slack_bot_token` block (after line 113):
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
if (site.linkedin_access_token) {
|
|
97
|
+
await initLinkedInClient(site.id, site.linkedin_access_token);
|
|
98
|
+
logToStderr(`Initialized LinkedIn for site: ${site.id}`);
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Step 4: Commit**
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
git add servers/wp-rest-bridge/build/wordpress.js
|
|
106
|
+
git commit -m "feat(bridge): aggiunge LinkedIn client helpers a wordpress.js
|
|
107
|
+
|
|
108
|
+
- initLinkedInClient() con OAuth 2.0 bearer token
|
|
109
|
+
- hasLinkedIn(), makeLinkedInRequest(), getLinkedInPersonUrn()
|
|
110
|
+
- Registrazione nel loop initWordPress()
|
|
111
|
+
- LinkedIn API v2 (Community Management API, versione 202401)"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
### Task 2: Create linkedin.js MCP tool file
|
|
117
|
+
|
|
118
|
+
**Files:**
|
|
119
|
+
- Create: `servers/wp-rest-bridge/build/tools/linkedin.js`
|
|
120
|
+
|
|
121
|
+
**Context:** Follow the exact pattern of `buffer.js` (5 tools, Zod schemas, tool definitions, handlers). LinkedIn Community Management API endpoints:
|
|
122
|
+
- `GET /userinfo` — get profile
|
|
123
|
+
- `POST /posts` — create post (with `author` URN)
|
|
124
|
+
- `POST /articles` — create article
|
|
125
|
+
- `GET /organizationalEntityShareStatistics` — analytics
|
|
126
|
+
- `GET /posts?author={urn}` — list posts
|
|
127
|
+
|
|
128
|
+
**Step 1: Create the file**
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
import { hasLinkedIn, makeLinkedInRequest, getLinkedInPersonUrn } from '../wordpress.js';
|
|
132
|
+
import { z } from 'zod';
|
|
133
|
+
|
|
134
|
+
// ── Zod Schemas ─────────────────────────────────────────────────
|
|
135
|
+
const liGetProfileSchema = z.object({}).strict();
|
|
136
|
+
|
|
137
|
+
const liCreatePostSchema = z.object({
|
|
138
|
+
text: z.string().describe('Post text content'),
|
|
139
|
+
link_url: z.string().optional().describe('URL to attach as link share'),
|
|
140
|
+
image_url: z.string().optional().describe('URL of image to attach'),
|
|
141
|
+
visibility: z.enum(['PUBLIC', 'CONNECTIONS']).optional().default('PUBLIC')
|
|
142
|
+
.describe('Post visibility (default: PUBLIC)'),
|
|
143
|
+
}).strict();
|
|
144
|
+
|
|
145
|
+
const liCreateArticleSchema = z.object({
|
|
146
|
+
title: z.string().describe('Article title'),
|
|
147
|
+
body_html: z.string().describe('Article body in HTML format'),
|
|
148
|
+
thumbnail_url: z.string().optional().describe('Thumbnail image URL'),
|
|
149
|
+
}).strict();
|
|
150
|
+
|
|
151
|
+
const liGetAnalyticsSchema = z.object({
|
|
152
|
+
post_id: z.string().optional().describe('Specific post URN for analytics (all posts if omitted)'),
|
|
153
|
+
period: z.enum(['day', 'month']).optional().default('month')
|
|
154
|
+
.describe('Time granularity (default: month)'),
|
|
155
|
+
}).strict();
|
|
156
|
+
|
|
157
|
+
const liListPostsSchema = z.object({
|
|
158
|
+
count: z.number().optional().default(10).describe('Number of posts to return (default 10)'),
|
|
159
|
+
start: z.number().optional().default(0).describe('Pagination start index'),
|
|
160
|
+
}).strict();
|
|
161
|
+
|
|
162
|
+
// ── Tool Definitions ────────────────────────────────────────────
|
|
163
|
+
export const linkedinTools = [
|
|
164
|
+
{
|
|
165
|
+
name: "li_get_profile",
|
|
166
|
+
description: "Gets the authenticated LinkedIn user profile (name, headline, vanity URL)",
|
|
167
|
+
inputSchema: {
|
|
168
|
+
type: "object",
|
|
169
|
+
properties: {},
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: "li_create_post",
|
|
174
|
+
description: "Creates a LinkedIn feed post (text, optional link or image)",
|
|
175
|
+
inputSchema: {
|
|
176
|
+
type: "object",
|
|
177
|
+
properties: {
|
|
178
|
+
text: { type: "string", description: "Post text content" },
|
|
179
|
+
link_url: { type: "string", description: "URL to attach as link share" },
|
|
180
|
+
image_url: { type: "string", description: "URL of image to attach" },
|
|
181
|
+
visibility: { type: "string", enum: ["PUBLIC", "CONNECTIONS"], description: "Post visibility (default: PUBLIC)" },
|
|
182
|
+
},
|
|
183
|
+
required: ["text"],
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: "li_create_article",
|
|
188
|
+
description: "Publishes a long-form LinkedIn article (blog-to-article)",
|
|
189
|
+
inputSchema: {
|
|
190
|
+
type: "object",
|
|
191
|
+
properties: {
|
|
192
|
+
title: { type: "string", description: "Article title" },
|
|
193
|
+
body_html: { type: "string", description: "Article body in HTML" },
|
|
194
|
+
thumbnail_url: { type: "string", description: "Thumbnail image URL" },
|
|
195
|
+
},
|
|
196
|
+
required: ["title", "body_html"],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: "li_get_analytics",
|
|
201
|
+
description: "Gets LinkedIn post analytics (impressions, clicks, engagement rate)",
|
|
202
|
+
inputSchema: {
|
|
203
|
+
type: "object",
|
|
204
|
+
properties: {
|
|
205
|
+
post_id: { type: "string", description: "Specific post URN (all posts if omitted)" },
|
|
206
|
+
period: { type: "string", enum: ["day", "month"], description: "Time granularity" },
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
name: "li_list_posts",
|
|
212
|
+
description: "Lists recent LinkedIn posts by the authenticated user",
|
|
213
|
+
inputSchema: {
|
|
214
|
+
type: "object",
|
|
215
|
+
properties: {
|
|
216
|
+
count: { type: "number", description: "Number of posts (default 10)" },
|
|
217
|
+
start: { type: "number", description: "Pagination start index" },
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
];
|
|
222
|
+
|
|
223
|
+
// ── Handlers ────────────────────────────────────────────────────
|
|
224
|
+
export const linkedinHandlers = {
|
|
225
|
+
li_get_profile: async (_params) => {
|
|
226
|
+
if (!hasLinkedIn()) {
|
|
227
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "LinkedIn not configured. Add linkedin_access_token to WP_SITES_CONFIG." }] } };
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
const response = await makeLinkedInRequest('GET', 'userinfo');
|
|
231
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] } };
|
|
232
|
+
} catch (error) {
|
|
233
|
+
const errorMessage = error.response?.data?.message || error.message;
|
|
234
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting LinkedIn profile: ${errorMessage}` }] } };
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
li_create_post: async (params) => {
|
|
239
|
+
if (!hasLinkedIn()) {
|
|
240
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "LinkedIn not configured. Add linkedin_access_token to WP_SITES_CONFIG." }] } };
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
const { text, link_url, image_url, visibility } = params;
|
|
244
|
+
const personUrn = getLinkedInPersonUrn();
|
|
245
|
+
const payload = {
|
|
246
|
+
author: personUrn,
|
|
247
|
+
lifecycleState: 'PUBLISHED',
|
|
248
|
+
visibility: visibility || 'PUBLIC',
|
|
249
|
+
commentary: text,
|
|
250
|
+
distribution: { feedDistribution: 'MAIN_FEED' },
|
|
251
|
+
};
|
|
252
|
+
if (link_url) {
|
|
253
|
+
payload.content = {
|
|
254
|
+
article: { source: link_url, title: text.substring(0, 200) },
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
const response = await makeLinkedInRequest('POST', 'posts', payload);
|
|
258
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify({ success: true, post_id: response.id || response['x-restli-id'] || 'created' }, null, 2) }] } };
|
|
259
|
+
} catch (error) {
|
|
260
|
+
const errorMessage = error.response?.data?.message || error.response?.data || error.message;
|
|
261
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error creating LinkedIn post: ${typeof errorMessage === 'string' ? errorMessage : JSON.stringify(errorMessage)}` }] } };
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
li_create_article: async (params) => {
|
|
266
|
+
if (!hasLinkedIn()) {
|
|
267
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "LinkedIn not configured. Add linkedin_access_token to WP_SITES_CONFIG." }] } };
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
const { title, body_html, thumbnail_url } = params;
|
|
271
|
+
const personUrn = getLinkedInPersonUrn();
|
|
272
|
+
const payload = {
|
|
273
|
+
author: personUrn,
|
|
274
|
+
lifecycleState: 'PUBLISHED',
|
|
275
|
+
visibility: 'PUBLIC',
|
|
276
|
+
commentary: title,
|
|
277
|
+
content: {
|
|
278
|
+
article: {
|
|
279
|
+
title,
|
|
280
|
+
description: body_html.replace(/<[^>]*>/g, '').substring(0, 256),
|
|
281
|
+
source: thumbnail_url || undefined,
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
distribution: { feedDistribution: 'MAIN_FEED' },
|
|
285
|
+
};
|
|
286
|
+
const response = await makeLinkedInRequest('POST', 'posts', payload);
|
|
287
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify({ success: true, article_id: response.id || 'created' }, null, 2) }] } };
|
|
288
|
+
} catch (error) {
|
|
289
|
+
const errorMessage = error.response?.data?.message || error.response?.data || error.message;
|
|
290
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error creating LinkedIn article: ${typeof errorMessage === 'string' ? errorMessage : JSON.stringify(errorMessage)}` }] } };
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
li_get_analytics: async (params) => {
|
|
295
|
+
if (!hasLinkedIn()) {
|
|
296
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "LinkedIn not configured. Add linkedin_access_token to WP_SITES_CONFIG." }] } };
|
|
297
|
+
}
|
|
298
|
+
try {
|
|
299
|
+
const personUrn = getLinkedInPersonUrn();
|
|
300
|
+
const queryParams = {
|
|
301
|
+
q: 'organizationalEntity',
|
|
302
|
+
organizationalEntity: personUrn,
|
|
303
|
+
};
|
|
304
|
+
if (params.period) queryParams.timeIntervals = `(timeRange:(start:0),timeGranularityType:${params.period.toUpperCase()})`;
|
|
305
|
+
const response = await makeLinkedInRequest('GET', 'organizationalEntityShareStatistics', queryParams);
|
|
306
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] } };
|
|
307
|
+
} catch (error) {
|
|
308
|
+
const errorMessage = error.response?.data?.message || error.message;
|
|
309
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting LinkedIn analytics: ${errorMessage}` }] } };
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
li_list_posts: async (params) => {
|
|
314
|
+
if (!hasLinkedIn()) {
|
|
315
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "LinkedIn not configured. Add linkedin_access_token to WP_SITES_CONFIG." }] } };
|
|
316
|
+
}
|
|
317
|
+
try {
|
|
318
|
+
const personUrn = getLinkedInPersonUrn();
|
|
319
|
+
const count = params.count || 10;
|
|
320
|
+
const start = params.start || 0;
|
|
321
|
+
const response = await makeLinkedInRequest('GET', `posts?author=${encodeURIComponent(personUrn)}&q=author&count=${count}&start=${start}`);
|
|
322
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] } };
|
|
323
|
+
} catch (error) {
|
|
324
|
+
const errorMessage = error.response?.data?.message || error.message;
|
|
325
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error listing LinkedIn posts: ${errorMessage}` }] } };
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**Step 2: Commit**
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
git add servers/wp-rest-bridge/build/tools/linkedin.js
|
|
335
|
+
git commit -m "feat(bridge): aggiunge linkedin.js con 5 MCP tools
|
|
336
|
+
|
|
337
|
+
Tools: li_get_profile, li_create_post, li_create_article,
|
|
338
|
+
li_get_analytics, li_list_posts
|
|
339
|
+
|
|
340
|
+
- LinkedIn Community Management API v2 (versione 202401)
|
|
341
|
+
- OAuth 2.0 bearer token authentication
|
|
342
|
+
- Post creation con link share e image support
|
|
343
|
+
- Article publishing per blog-to-LinkedIn workflow"
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
---
|
|
347
|
+
|
|
348
|
+
### Task 3: Add Twitter client helpers to wordpress.js
|
|
349
|
+
|
|
350
|
+
**Files:**
|
|
351
|
+
- Modify: `servers/wp-rest-bridge/build/wordpress.js`
|
|
352
|
+
|
|
353
|
+
**Context:** Twitter API v2 uses OAuth 1.0a for posting (user context) via `oauth-1.0a` or simply Bearer token for read. For simplicity, we use Bearer token for reads and API key+secret+access token for writes. Base URL: `https://api.twitter.com/2/`.
|
|
354
|
+
|
|
355
|
+
**Step 1: Add Twitter client Map and init function**
|
|
356
|
+
|
|
357
|
+
Add after `const liSiteClients = new Map();`:
|
|
358
|
+
|
|
359
|
+
```javascript
|
|
360
|
+
const twSiteClients = new Map();
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
Add after the `initLinkedInClient` function:
|
|
364
|
+
|
|
365
|
+
```javascript
|
|
366
|
+
async function initTwitterClient(id, bearerToken) {
|
|
367
|
+
const client = axios.create({
|
|
368
|
+
baseURL: 'https://api.twitter.com/2/',
|
|
369
|
+
headers: {
|
|
370
|
+
'Content-Type': 'application/json',
|
|
371
|
+
'Authorization': `Bearer ${bearerToken}`,
|
|
372
|
+
},
|
|
373
|
+
timeout: DEFAULT_TIMEOUT_MS,
|
|
374
|
+
});
|
|
375
|
+
twSiteClients.set(id, client);
|
|
376
|
+
}
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**Step 2: Add Twitter has/make helpers**
|
|
380
|
+
|
|
381
|
+
Add after the LinkedIn helpers section:
|
|
382
|
+
|
|
383
|
+
```javascript
|
|
384
|
+
// ── Twitter/X Interface ─────────────────────────────────────────
|
|
385
|
+
export function hasTwitter(siteId) {
|
|
386
|
+
const id = siteId || activeSiteId;
|
|
387
|
+
return twSiteClients.has(id);
|
|
388
|
+
}
|
|
389
|
+
export async function makeTwitterRequest(method, endpoint, data, siteId) {
|
|
390
|
+
const id = siteId || activeSiteId;
|
|
391
|
+
const client = twSiteClients.get(id);
|
|
392
|
+
if (!client) {
|
|
393
|
+
throw new Error(`Twitter not configured for site "${id}". Add twitter_bearer_token to WP_SITES_CONFIG.`);
|
|
394
|
+
}
|
|
395
|
+
const limiter = getLimiter(id);
|
|
396
|
+
await limiter.acquire();
|
|
397
|
+
try {
|
|
398
|
+
const response = await client.request({ method, url: endpoint, data: method !== 'GET' ? data : undefined, params: method === 'GET' ? data : undefined });
|
|
399
|
+
return response.data;
|
|
400
|
+
} finally {
|
|
401
|
+
limiter.release();
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
export function getTwitterUserId(siteId) {
|
|
405
|
+
const id = siteId || activeSiteId;
|
|
406
|
+
const sites = JSON.parse(process.env.WP_SITES_CONFIG || '[]');
|
|
407
|
+
const site = sites.find((s) => s.id === id);
|
|
408
|
+
if (!site?.twitter_user_id) {
|
|
409
|
+
throw new Error(`Twitter user ID not configured for site "${id}". Add twitter_user_id to WP_SITES_CONFIG.`);
|
|
410
|
+
}
|
|
411
|
+
return site.twitter_user_id;
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
**Step 3: Register Twitter in initWordPress() loop**
|
|
416
|
+
|
|
417
|
+
Add after the LinkedIn registration block:
|
|
418
|
+
|
|
419
|
+
```javascript
|
|
420
|
+
if (site.twitter_bearer_token) {
|
|
421
|
+
await initTwitterClient(site.id, site.twitter_bearer_token);
|
|
422
|
+
logToStderr(`Initialized Twitter for site: ${site.id}`);
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
**Step 4: Commit**
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
git add servers/wp-rest-bridge/build/wordpress.js
|
|
430
|
+
git commit -m "feat(bridge): aggiunge Twitter/X client helpers a wordpress.js
|
|
431
|
+
|
|
432
|
+
- initTwitterClient() con Bearer token (API v2)
|
|
433
|
+
- hasTwitter(), makeTwitterRequest(), getTwitterUserId()
|
|
434
|
+
- Registrazione nel loop initWordPress()"
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
### Task 4: Create twitter.js MCP tool file
|
|
440
|
+
|
|
441
|
+
**Files:**
|
|
442
|
+
- Create: `servers/wp-rest-bridge/build/tools/twitter.js`
|
|
443
|
+
|
|
444
|
+
**Step 1: Create the file**
|
|
445
|
+
|
|
446
|
+
```javascript
|
|
447
|
+
import { hasTwitter, makeTwitterRequest, getTwitterUserId } from '../wordpress.js';
|
|
448
|
+
import { z } from 'zod';
|
|
449
|
+
|
|
450
|
+
// ── Zod Schemas ─────────────────────────────────────────────────
|
|
451
|
+
const twCreateTweetSchema = z.object({
|
|
452
|
+
text: z.string().max(280).describe('Tweet text (max 280 characters)'),
|
|
453
|
+
media_ids: z.array(z.string()).optional().describe('Array of media IDs to attach'),
|
|
454
|
+
reply_to: z.string().optional().describe('Tweet ID to reply to (for threads)'),
|
|
455
|
+
}).strict();
|
|
456
|
+
|
|
457
|
+
const twCreateThreadSchema = z.object({
|
|
458
|
+
tweets: z.array(z.string().max(280)).min(2)
|
|
459
|
+
.describe('Array of tweet texts (min 2, each max 280 chars)'),
|
|
460
|
+
}).strict();
|
|
461
|
+
|
|
462
|
+
const twGetMetricsSchema = z.object({
|
|
463
|
+
tweet_id: z.string().describe('Tweet ID to get metrics for'),
|
|
464
|
+
}).strict();
|
|
465
|
+
|
|
466
|
+
const twListTweetsSchema = z.object({
|
|
467
|
+
count: z.number().optional().default(10).describe('Number of tweets (default 10, max 100)'),
|
|
468
|
+
since: z.string().optional().describe('Only tweets after this ISO 8601 date'),
|
|
469
|
+
}).strict();
|
|
470
|
+
|
|
471
|
+
const twDeleteTweetSchema = z.object({
|
|
472
|
+
tweet_id: z.string().describe('Tweet ID to delete'),
|
|
473
|
+
}).strict();
|
|
474
|
+
|
|
475
|
+
// ── Tool Definitions ────────────────────────────────────────────
|
|
476
|
+
export const twitterTools = [
|
|
477
|
+
{
|
|
478
|
+
name: "tw_create_tweet",
|
|
479
|
+
description: "Publishes a tweet (text, optional media, optional reply-to for threads)",
|
|
480
|
+
inputSchema: {
|
|
481
|
+
type: "object",
|
|
482
|
+
properties: {
|
|
483
|
+
text: { type: "string", description: "Tweet text (max 280 characters)" },
|
|
484
|
+
media_ids: { type: "array", items: { type: "string" }, description: "Media IDs to attach" },
|
|
485
|
+
reply_to: { type: "string", description: "Tweet ID to reply to" },
|
|
486
|
+
},
|
|
487
|
+
required: ["text"],
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
name: "tw_create_thread",
|
|
492
|
+
description: "Publishes a connected Twitter thread (multiple tweets linked as replies)",
|
|
493
|
+
inputSchema: {
|
|
494
|
+
type: "object",
|
|
495
|
+
properties: {
|
|
496
|
+
tweets: { type: "array", items: { type: "string" }, description: "Array of tweet texts (min 2)" },
|
|
497
|
+
},
|
|
498
|
+
required: ["tweets"],
|
|
499
|
+
},
|
|
500
|
+
},
|
|
501
|
+
{
|
|
502
|
+
name: "tw_get_metrics",
|
|
503
|
+
description: "Gets tweet metrics (impressions, likes, retweets, replies, quotes)",
|
|
504
|
+
inputSchema: {
|
|
505
|
+
type: "object",
|
|
506
|
+
properties: {
|
|
507
|
+
tweet_id: { type: "string", description: "Tweet ID" },
|
|
508
|
+
},
|
|
509
|
+
required: ["tweet_id"],
|
|
510
|
+
},
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
name: "tw_list_tweets",
|
|
514
|
+
description: "Lists recent tweets by the authenticated user",
|
|
515
|
+
inputSchema: {
|
|
516
|
+
type: "object",
|
|
517
|
+
properties: {
|
|
518
|
+
count: { type: "number", description: "Number of tweets (default 10)" },
|
|
519
|
+
since: { type: "string", description: "Only tweets after this ISO 8601 date" },
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
name: "tw_delete_tweet",
|
|
525
|
+
description: "Deletes a tweet by ID (irreversible)",
|
|
526
|
+
inputSchema: {
|
|
527
|
+
type: "object",
|
|
528
|
+
properties: {
|
|
529
|
+
tweet_id: { type: "string", description: "Tweet ID to delete" },
|
|
530
|
+
},
|
|
531
|
+
required: ["tweet_id"],
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
];
|
|
535
|
+
|
|
536
|
+
// ── Handlers ────────────────────────────────────────────────────
|
|
537
|
+
export const twitterHandlers = {
|
|
538
|
+
tw_create_tweet: async (params) => {
|
|
539
|
+
if (!hasTwitter()) {
|
|
540
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "Twitter not configured. Add twitter_bearer_token to WP_SITES_CONFIG." }] } };
|
|
541
|
+
}
|
|
542
|
+
try {
|
|
543
|
+
const { text, media_ids, reply_to } = params;
|
|
544
|
+
const payload = { text };
|
|
545
|
+
if (media_ids?.length) payload.media = { media_ids };
|
|
546
|
+
if (reply_to) payload.reply = { in_reply_to_tweet_id: reply_to };
|
|
547
|
+
const response = await makeTwitterRequest('POST', 'tweets', payload);
|
|
548
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] } };
|
|
549
|
+
} catch (error) {
|
|
550
|
+
const errorMessage = error.response?.data?.detail || error.response?.data?.title || error.message;
|
|
551
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error creating tweet: ${errorMessage}` }] } };
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
|
|
555
|
+
tw_create_thread: async (params) => {
|
|
556
|
+
if (!hasTwitter()) {
|
|
557
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "Twitter not configured. Add twitter_bearer_token to WP_SITES_CONFIG." }] } };
|
|
558
|
+
}
|
|
559
|
+
try {
|
|
560
|
+
const { tweets } = params;
|
|
561
|
+
if (!tweets || tweets.length < 2) {
|
|
562
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "A thread requires at least 2 tweets." }] } };
|
|
563
|
+
}
|
|
564
|
+
const results = [];
|
|
565
|
+
let lastTweetId = null;
|
|
566
|
+
for (const tweetText of tweets) {
|
|
567
|
+
const payload = { text: tweetText };
|
|
568
|
+
if (lastTweetId) payload.reply = { in_reply_to_tweet_id: lastTweetId };
|
|
569
|
+
const response = await makeTwitterRequest('POST', 'tweets', payload);
|
|
570
|
+
lastTweetId = response.data?.id;
|
|
571
|
+
results.push({ id: lastTweetId, text: tweetText });
|
|
572
|
+
}
|
|
573
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify({ thread_length: results.length, tweets: results }, null, 2) }] } };
|
|
574
|
+
} catch (error) {
|
|
575
|
+
const errorMessage = error.response?.data?.detail || error.message;
|
|
576
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error creating thread: ${errorMessage}` }] } };
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
|
|
580
|
+
tw_get_metrics: async (params) => {
|
|
581
|
+
if (!hasTwitter()) {
|
|
582
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "Twitter not configured. Add twitter_bearer_token to WP_SITES_CONFIG." }] } };
|
|
583
|
+
}
|
|
584
|
+
try {
|
|
585
|
+
const { tweet_id } = params;
|
|
586
|
+
const response = await makeTwitterRequest('GET', `tweets/${tweet_id}?tweet.fields=public_metrics,created_at,text`);
|
|
587
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] } };
|
|
588
|
+
} catch (error) {
|
|
589
|
+
const errorMessage = error.response?.data?.detail || error.message;
|
|
590
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error getting tweet metrics: ${errorMessage}` }] } };
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
|
|
594
|
+
tw_list_tweets: async (params) => {
|
|
595
|
+
if (!hasTwitter()) {
|
|
596
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "Twitter not configured. Add twitter_bearer_token to WP_SITES_CONFIG." }] } };
|
|
597
|
+
}
|
|
598
|
+
try {
|
|
599
|
+
const userId = getTwitterUserId();
|
|
600
|
+
const count = Math.min(params.count || 10, 100);
|
|
601
|
+
let endpoint = `users/${userId}/tweets?max_results=${count}&tweet.fields=public_metrics,created_at,text`;
|
|
602
|
+
if (params.since) endpoint += `&start_time=${params.since}`;
|
|
603
|
+
const response = await makeTwitterRequest('GET', endpoint);
|
|
604
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }] } };
|
|
605
|
+
} catch (error) {
|
|
606
|
+
const errorMessage = error.response?.data?.detail || error.message;
|
|
607
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error listing tweets: ${errorMessage}` }] } };
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
|
|
611
|
+
tw_delete_tweet: async (params) => {
|
|
612
|
+
if (!hasTwitter()) {
|
|
613
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "Twitter not configured. Add twitter_bearer_token to WP_SITES_CONFIG." }] } };
|
|
614
|
+
}
|
|
615
|
+
try {
|
|
616
|
+
const { tweet_id } = params;
|
|
617
|
+
const response = await makeTwitterRequest('DELETE', `tweets/${tweet_id}`);
|
|
618
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify({ deleted: true, tweet_id }, null, 2) }] } };
|
|
619
|
+
} catch (error) {
|
|
620
|
+
const errorMessage = error.response?.data?.detail || error.message;
|
|
621
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error deleting tweet: ${errorMessage}` }] } };
|
|
622
|
+
}
|
|
623
|
+
},
|
|
624
|
+
};
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
**Step 2: Commit**
|
|
628
|
+
|
|
629
|
+
```bash
|
|
630
|
+
git add servers/wp-rest-bridge/build/tools/twitter.js
|
|
631
|
+
git commit -m "feat(bridge): aggiunge twitter.js con 5 MCP tools
|
|
632
|
+
|
|
633
|
+
Tools: tw_create_tweet, tw_create_thread, tw_get_metrics,
|
|
634
|
+
tw_list_tweets, tw_delete_tweet
|
|
635
|
+
|
|
636
|
+
- Twitter API v2 con Bearer token
|
|
637
|
+
- Thread creation sequenziale con reply chaining
|
|
638
|
+
- Public metrics (impressions, likes, retweets, replies)
|
|
639
|
+
- Delete con safety hook (definito in Task 7)"
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
---
|
|
643
|
+
|
|
644
|
+
### Task 5: Register LinkedIn and Twitter in index.js
|
|
645
|
+
|
|
646
|
+
**Files:**
|
|
647
|
+
- Modify: `servers/wp-rest-bridge/build/tools/index.js`
|
|
648
|
+
|
|
649
|
+
**Step 1: Add imports and registration**
|
|
650
|
+
|
|
651
|
+
Add after the `import { wcWorkflowTools, wcWorkflowHandlers } from './wc-workflows.js';` line:
|
|
652
|
+
|
|
653
|
+
```javascript
|
|
654
|
+
import { linkedinTools, linkedinHandlers } from './linkedin.js';
|
|
655
|
+
import { twitterTools, twitterHandlers } from './twitter.js';
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
Add to `allTools` array after `...wcWorkflowTools, // 4 tools`:
|
|
659
|
+
|
|
660
|
+
```javascript
|
|
661
|
+
...linkedinTools, // 5 tools
|
|
662
|
+
...twitterTools, // 5 tools
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
Add to `toolHandlers` object after `...wcWorkflowHandlers,`:
|
|
666
|
+
|
|
667
|
+
```javascript
|
|
668
|
+
...linkedinHandlers,
|
|
669
|
+
...twitterHandlers,
|
|
670
|
+
```
|
|
671
|
+
|
|
672
|
+
**Step 2: Commit**
|
|
673
|
+
|
|
674
|
+
```bash
|
|
675
|
+
git add servers/wp-rest-bridge/build/tools/index.js
|
|
676
|
+
git commit -m "feat(bridge): registra LinkedIn e Twitter tools in index.js
|
|
677
|
+
|
|
678
|
+
Tool count: 132 → 142 (+10 tools)
|
|
679
|
+
- 5 LinkedIn tools (li_*)
|
|
680
|
+
- 5 Twitter tools (tw_*)"
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
---
|
|
684
|
+
|
|
685
|
+
### Task 6: Create wp-linkedin skill
|
|
686
|
+
|
|
687
|
+
**Files:**
|
|
688
|
+
- Create: `skills/wp-linkedin/SKILL.md`
|
|
689
|
+
- Create: `skills/wp-linkedin/scripts/linkedin_inspect.mjs`
|
|
690
|
+
- Create: `skills/wp-linkedin/references/linkedin-setup.md`
|
|
691
|
+
- Create: `skills/wp-linkedin/references/linkedin-posting.md`
|
|
692
|
+
- Create: `skills/wp-linkedin/references/linkedin-analytics.md`
|
|
693
|
+
|
|
694
|
+
**Step 1: Create SKILL.md**
|
|
695
|
+
|
|
696
|
+
Write to `skills/wp-linkedin/SKILL.md`:
|
|
697
|
+
|
|
698
|
+
```markdown
|
|
699
|
+
---
|
|
700
|
+
name: wp-linkedin
|
|
701
|
+
description: This skill should be used when the user asks to "publish to LinkedIn",
|
|
702
|
+
"LinkedIn post", "LinkedIn article", "B2B social", "pubblica su LinkedIn",
|
|
703
|
+
"LinkedIn analytics", "LinkedIn engagement", or mentions LinkedIn publishing
|
|
704
|
+
and analytics for WordPress content.
|
|
705
|
+
version: 1.0.0
|
|
706
|
+
tags: [linkedin, social-media, b2b, direct-social]
|
|
707
|
+
---
|
|
708
|
+
|
|
709
|
+
# WordPress LinkedIn Integration Skill
|
|
710
|
+
|
|
711
|
+
## Overview
|
|
712
|
+
|
|
713
|
+
Direct LinkedIn publishing connects WordPress content to LinkedIn via the Community Management API v2. This enables B2B-focused content distribution: feed posts for quick updates, long-form articles for blog-to-LinkedIn workflows, and analytics for engagement tracking. The WP REST Bridge provides 5 MCP tools with the `li_*` namespace.
|
|
714
|
+
|
|
715
|
+
## When to Use
|
|
716
|
+
|
|
717
|
+
- User wants to publish a WordPress blog post to LinkedIn
|
|
718
|
+
- User needs to create LinkedIn feed posts from WordPress content
|
|
719
|
+
- User wants to publish a long-form LinkedIn article from a blog post
|
|
720
|
+
- User asks about LinkedIn post analytics (impressions, clicks, engagement)
|
|
721
|
+
- User needs B2B social media distribution from WordPress
|
|
722
|
+
|
|
723
|
+
## Decision Tree
|
|
724
|
+
|
|
725
|
+
1. **What LinkedIn action?**
|
|
726
|
+
- "publish post" / "LinkedIn update" / "feed post" → Posting workflow (Section 1)
|
|
727
|
+
- "article" / "long-form" / "blog to LinkedIn" → Article publishing (Section 2)
|
|
728
|
+
- "analytics" / "engagement" / "impressions" → Analytics (Section 3)
|
|
729
|
+
- "setup" / "configure" / "connect LinkedIn" → Setup guide (Section 4)
|
|
730
|
+
|
|
731
|
+
2. **Run detection first:**
|
|
732
|
+
```bash
|
|
733
|
+
node skills/wp-linkedin/scripts/linkedin_inspect.mjs [--cwd=/path/to/project]
|
|
734
|
+
```
|
|
735
|
+
This identifies configured LinkedIn credentials in WP_SITES_CONFIG.
|
|
736
|
+
|
|
737
|
+
## Sections
|
|
738
|
+
|
|
739
|
+
### Section 1: LinkedIn Posting
|
|
740
|
+
See `references/linkedin-posting.md`
|
|
741
|
+
- Feed post creation with text, links, and images
|
|
742
|
+
- Visibility settings (PUBLIC vs CONNECTIONS)
|
|
743
|
+
- Content adaptation from WordPress excerpt/title
|
|
744
|
+
- Hashtag strategy and mention formatting
|
|
745
|
+
|
|
746
|
+
### Section 2: Article Publishing
|
|
747
|
+
See `references/linkedin-posting.md`
|
|
748
|
+
- Blog post → LinkedIn article conversion
|
|
749
|
+
- HTML content formatting for LinkedIn
|
|
750
|
+
- Thumbnail and media handling
|
|
751
|
+
- Article vs post decision criteria
|
|
752
|
+
|
|
753
|
+
### Section 3: Analytics
|
|
754
|
+
See `references/linkedin-analytics.md`
|
|
755
|
+
- Post impressions and engagement metrics
|
|
756
|
+
- Click-through rates and share counts
|
|
757
|
+
- Performance comparison across posts
|
|
758
|
+
- B2B content performance benchmarks
|
|
759
|
+
|
|
760
|
+
### Section 4: Setup Guide
|
|
761
|
+
See `references/linkedin-setup.md`
|
|
762
|
+
- LinkedIn Developer App creation
|
|
763
|
+
- OAuth 2.0 access token generation
|
|
764
|
+
- WP_SITES_CONFIG configuration
|
|
765
|
+
- Required scopes: w_member_social, r_liteprofile
|
|
766
|
+
|
|
767
|
+
## Reference Files
|
|
768
|
+
|
|
769
|
+
| File | Content |
|
|
770
|
+
|------|---------|
|
|
771
|
+
| `references/linkedin-setup.md` | Developer app, OAuth setup, WP_SITES_CONFIG, scopes |
|
|
772
|
+
| `references/linkedin-posting.md` | Post creation, articles, content adaptation, formatting |
|
|
773
|
+
| `references/linkedin-analytics.md` | Metrics, engagement tracking, performance benchmarks |
|
|
774
|
+
|
|
775
|
+
## MCP Tools
|
|
776
|
+
|
|
777
|
+
| Tool | Description |
|
|
778
|
+
|------|-------------|
|
|
779
|
+
| `li_get_profile` | Get authenticated LinkedIn user profile |
|
|
780
|
+
| `li_create_post` | Create a LinkedIn feed post (text, link, image) |
|
|
781
|
+
| `li_create_article` | Publish a long-form LinkedIn article |
|
|
782
|
+
| `li_get_analytics` | Get post analytics (impressions, clicks, engagement) |
|
|
783
|
+
| `li_list_posts` | List recent posts by the authenticated user |
|
|
784
|
+
|
|
785
|
+
## Recommended Agent
|
|
786
|
+
|
|
787
|
+
Use the **`wp-distribution-manager`** agent for multi-channel workflows that combine LinkedIn with Mailchimp, Buffer, SendGrid, and Twitter/X.
|
|
788
|
+
|
|
789
|
+
## Related Skills
|
|
790
|
+
|
|
791
|
+
- **`wp-twitter`** — Twitter/X direct publishing (awareness channel)
|
|
792
|
+
- **`wp-social-email`** — Mailchimp, Buffer, SendGrid distribution
|
|
793
|
+
- **`wp-content-repurposing`** — Transform blog content into LinkedIn-optimized formats
|
|
794
|
+
- **`wp-content`** — Create source WordPress content for distribution
|
|
795
|
+
```
|
|
796
|
+
|
|
797
|
+
**Step 2: Create linkedin_inspect.mjs**
|
|
798
|
+
|
|
799
|
+
Write to `skills/wp-linkedin/scripts/linkedin_inspect.mjs`:
|
|
800
|
+
|
|
801
|
+
```javascript
|
|
802
|
+
/**
|
|
803
|
+
* linkedin_inspect.mjs — Detect LinkedIn configuration readiness.
|
|
804
|
+
*
|
|
805
|
+
* Checks WP_SITES_CONFIG for LinkedIn credentials.
|
|
806
|
+
*
|
|
807
|
+
* Usage:
|
|
808
|
+
* node linkedin_inspect.mjs [--cwd=/path/to/project]
|
|
809
|
+
*
|
|
810
|
+
* Exit codes:
|
|
811
|
+
* 0 — LinkedIn configuration found
|
|
812
|
+
* 1 — no LinkedIn configuration found
|
|
813
|
+
*/
|
|
814
|
+
|
|
815
|
+
import { stdout, exit, argv } from 'node:process';
|
|
816
|
+
import { resolve } from 'node:path';
|
|
817
|
+
|
|
818
|
+
function detectLinkedInConfig() {
|
|
819
|
+
const li = { configured: false, indicators: [] };
|
|
820
|
+
const raw = process.env.WP_SITES_CONFIG;
|
|
821
|
+
if (!raw) return li;
|
|
822
|
+
|
|
823
|
+
let sites;
|
|
824
|
+
try { sites = JSON.parse(raw); } catch { return li; }
|
|
825
|
+
if (!Array.isArray(sites)) return li;
|
|
826
|
+
|
|
827
|
+
for (const site of sites) {
|
|
828
|
+
const label = site.id || site.url || 'unknown';
|
|
829
|
+
if (site.linkedin_access_token) {
|
|
830
|
+
li.configured = true;
|
|
831
|
+
li.indicators.push(`linkedin_access_token configured for ${label}`);
|
|
832
|
+
}
|
|
833
|
+
if (site.linkedin_person_urn) {
|
|
834
|
+
li.indicators.push(`linkedin_person_urn: ${site.linkedin_person_urn} for ${label}`);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
return li;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function main() {
|
|
841
|
+
const cwdArg = argv.find(a => a.startsWith('--cwd='));
|
|
842
|
+
const cwd = cwdArg ? resolve(cwdArg.split('=')[1]) : process.cwd();
|
|
843
|
+
|
|
844
|
+
const linkedin = detectLinkedInConfig();
|
|
845
|
+
|
|
846
|
+
const report = {
|
|
847
|
+
linkedin_configured: linkedin.configured,
|
|
848
|
+
linkedin,
|
|
849
|
+
cwd,
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
853
|
+
exit(linkedin.configured ? 0 : 1);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
main();
|
|
857
|
+
```
|
|
858
|
+
|
|
859
|
+
**Step 3: Create reference files**
|
|
860
|
+
|
|
861
|
+
Write `skills/wp-linkedin/references/linkedin-setup.md`, `linkedin-posting.md`, `linkedin-analytics.md` with setup instructions, posting patterns, and analytics reference. Each ~50-80 lines covering API setup, config format, and usage patterns. (Follow the structure of `skills/wp-alerting/references/*.md` as template.)
|
|
862
|
+
|
|
863
|
+
**Step 4: Commit**
|
|
864
|
+
|
|
865
|
+
```bash
|
|
866
|
+
git add skills/wp-linkedin/
|
|
867
|
+
git commit -m "feat: aggiunge skill wp-linkedin (setup, posting, analytics)
|
|
868
|
+
|
|
869
|
+
- SKILL.md con decision tree e 5 MCP tools
|
|
870
|
+
- linkedin_inspect.mjs detection script
|
|
871
|
+
- 3 reference files (setup, posting, analytics)"
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
---
|
|
875
|
+
|
|
876
|
+
### Task 7: Create wp-twitter skill
|
|
877
|
+
|
|
878
|
+
**Files:**
|
|
879
|
+
- Create: `skills/wp-twitter/SKILL.md`
|
|
880
|
+
- Create: `skills/wp-twitter/scripts/twitter_inspect.mjs`
|
|
881
|
+
- Create: `skills/wp-twitter/references/twitter-setup.md`
|
|
882
|
+
- Create: `skills/wp-twitter/references/twitter-posting.md`
|
|
883
|
+
- Create: `skills/wp-twitter/references/twitter-analytics.md`
|
|
884
|
+
|
|
885
|
+
**Context:** Same structure as Task 6 but for Twitter/X. Adapt trigger phrases, tool names (`tw_*`), and API specifics.
|
|
886
|
+
|
|
887
|
+
**Step 1-4:** Mirror the LinkedIn skill creation pattern with Twitter-specific content. SKILL.md triggers: "Twitter", "X", "tweet", "thread", "pubblica tweet", "Twitter analytics". Detection script checks `twitter_bearer_token` and `twitter_user_id`. References cover API v2 setup, tweet/thread posting, and metrics.
|
|
888
|
+
|
|
889
|
+
**Step 5: Commit**
|
|
890
|
+
|
|
891
|
+
```bash
|
|
892
|
+
git add skills/wp-twitter/
|
|
893
|
+
git commit -m "feat: aggiunge skill wp-twitter (setup, posting, thread, analytics)
|
|
894
|
+
|
|
895
|
+
- SKILL.md con decision tree e 5 MCP tools
|
|
896
|
+
- twitter_inspect.mjs detection script
|
|
897
|
+
- 3 reference files (setup, posting, analytics)"
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
---
|
|
901
|
+
|
|
902
|
+
### Task 8: Update hooks.json with new safety hooks
|
|
903
|
+
|
|
904
|
+
**Files:**
|
|
905
|
+
- Modify: `hooks/hooks.json`
|
|
906
|
+
|
|
907
|
+
**Step 1: Add tw_delete_tweet hook**
|
|
908
|
+
|
|
909
|
+
Add after the `wf_delete_trigger` entry:
|
|
910
|
+
|
|
911
|
+
```json
|
|
912
|
+
{
|
|
913
|
+
"matcher": "mcp__wp-rest-bridge__tw_delete_tweet",
|
|
914
|
+
"hooks": [
|
|
915
|
+
{
|
|
916
|
+
"type": "prompt",
|
|
917
|
+
"prompt": "The agent is about to DELETE a tweet. This is irreversible. Verify the user explicitly requested this deletion. Respond 'approve' only if intentional."
|
|
918
|
+
}
|
|
919
|
+
]
|
|
920
|
+
}
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
**Step 2: Add li_create_article hook**
|
|
924
|
+
|
|
925
|
+
```json
|
|
926
|
+
{
|
|
927
|
+
"matcher": "mcp__wp-rest-bridge__li_create_article",
|
|
928
|
+
"hooks": [
|
|
929
|
+
{
|
|
930
|
+
"type": "prompt",
|
|
931
|
+
"prompt": "The agent is about to PUBLISH a long-form article on LinkedIn. This will be publicly visible. Verify the user has reviewed the content and explicitly requested publication. Respond 'approve' only if intentional."
|
|
932
|
+
}
|
|
933
|
+
]
|
|
934
|
+
}
|
|
935
|
+
```
|
|
936
|
+
|
|
937
|
+
**Step 3: Commit**
|
|
938
|
+
|
|
939
|
+
```bash
|
|
940
|
+
git add hooks/hooks.json
|
|
941
|
+
git commit -m "feat: aggiunge safety hooks per Twitter delete e LinkedIn article
|
|
942
|
+
|
|
943
|
+
- tw_delete_tweet: conferma eliminazione tweet
|
|
944
|
+
- li_create_article: conferma pubblicazione articolo LinkedIn
|
|
945
|
+
- Total hooks: 10 → 12"
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
---
|
|
949
|
+
|
|
950
|
+
### Task 9: Update router decision-tree.md to v17
|
|
951
|
+
|
|
952
|
+
**Files:**
|
|
953
|
+
- Modify: `skills/wordpress-router/references/decision-tree.md`
|
|
954
|
+
|
|
955
|
+
**Step 1: Update header**
|
|
956
|
+
|
|
957
|
+
Change line 1 from `v16` to `v17` and add `+ LinkedIn + Twitter/X` to the end.
|
|
958
|
+
|
|
959
|
+
**Step 2: Add keywords to operations block (line 17)**
|
|
960
|
+
|
|
961
|
+
Add to the operations keywords string: `LinkedIn, LinkedIn post, LinkedIn article, B2B social, pubblica LinkedIn, Twitter, X, tweet, thread, pubblica tweet, Twitter analytics`
|
|
962
|
+
|
|
963
|
+
**Step 3: Add routing entries to Step 2b (after line 118)**
|
|
964
|
+
|
|
965
|
+
```markdown
|
|
966
|
+
- **LinkedIn / LinkedIn post / LinkedIn article / B2B social / pubblica LinkedIn**
|
|
967
|
+
→ `wp-linkedin` skill + `wp-distribution-manager` agent
|
|
968
|
+
- **Twitter / X / tweet / thread / pubblica tweet / Twitter analytics**
|
|
969
|
+
→ `wp-twitter` skill + `wp-distribution-manager` agent
|
|
970
|
+
```
|
|
971
|
+
|
|
972
|
+
**Step 4: Commit**
|
|
973
|
+
|
|
974
|
+
```bash
|
|
975
|
+
git add skills/wordpress-router/references/decision-tree.md
|
|
976
|
+
git commit -m "feat(router): aggiorna decision-tree a v17
|
|
977
|
+
|
|
978
|
+
- +2 categorie routing: LinkedIn e Twitter/X
|
|
979
|
+
- Aggiunge keywords operativi per social diretto
|
|
980
|
+
- 17 → 19 categorie totali"
|
|
981
|
+
```
|
|
982
|
+
|
|
983
|
+
---
|
|
984
|
+
|
|
985
|
+
### Task 10: Update wp-distribution-manager agent
|
|
986
|
+
|
|
987
|
+
**Files:**
|
|
988
|
+
- Modify: `agents/wp-distribution-manager.md`
|
|
989
|
+
|
|
990
|
+
**Step 1: Update description**
|
|
991
|
+
|
|
992
|
+
Add to the description block (line 5-6 area): ", LinkedIn direct posting, and Twitter/X publishing"
|
|
993
|
+
|
|
994
|
+
**Step 2: Add examples for LinkedIn and Twitter**
|
|
995
|
+
|
|
996
|
+
Add 2 examples after existing ones:
|
|
997
|
+
|
|
998
|
+
```markdown
|
|
999
|
+
<example>
|
|
1000
|
+
Context: User wants to publish a blog post to LinkedIn.
|
|
1001
|
+
user: "Pubblica il mio ultimo articolo su LinkedIn"
|
|
1002
|
+
assistant: "I'll use the wp-distribution-manager agent to fetch the post and create a LinkedIn article."
|
|
1003
|
+
<commentary>Direct LinkedIn publishing requires the distribution agent for content adaptation and API posting.</commentary>
|
|
1004
|
+
</example>
|
|
1005
|
+
|
|
1006
|
+
<example>
|
|
1007
|
+
Context: User wants to create a Twitter thread from a blog post.
|
|
1008
|
+
user: "Turn my blog post into a Twitter thread"
|
|
1009
|
+
assistant: "I'll use the wp-distribution-manager agent to split the post into thread-sized tweets and publish them."
|
|
1010
|
+
<commentary>Twitter thread creation requires content splitting and sequential posting.</commentary>
|
|
1011
|
+
</example>
|
|
1012
|
+
```
|
|
1013
|
+
|
|
1014
|
+
**Step 3: Update Procedures section**
|
|
1015
|
+
|
|
1016
|
+
Add Procedure 6 and 7 for LinkedIn and Twitter posting flows.
|
|
1017
|
+
|
|
1018
|
+
**Step 4: Update Related Skills**
|
|
1019
|
+
|
|
1020
|
+
Add: `- **wp-linkedin** -- Direct LinkedIn posting and analytics`
|
|
1021
|
+
Add: `- **wp-twitter** -- Direct Twitter/X posting and analytics`
|
|
1022
|
+
|
|
1023
|
+
**Step 5: Commit**
|
|
1024
|
+
|
|
1025
|
+
```bash
|
|
1026
|
+
git add agents/wp-distribution-manager.md
|
|
1027
|
+
git commit -m "feat(agent): aggiorna wp-distribution-manager per LinkedIn e Twitter
|
|
1028
|
+
|
|
1029
|
+
- Aggiunge descrizione LinkedIn + Twitter/X
|
|
1030
|
+
- +2 esempi (LinkedIn article, Twitter thread)
|
|
1031
|
+
- +2 procedure (LinkedIn posting, Twitter posting)
|
|
1032
|
+
- Aggiorna Related Skills"
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
---
|
|
1036
|
+
|
|
1037
|
+
### Task 11: Bump version to v2.10.0, update CHANGELOG
|
|
1038
|
+
|
|
1039
|
+
**Files:**
|
|
1040
|
+
- Modify: `package.json` (version: "2.10.0")
|
|
1041
|
+
- Modify: `CHANGELOG.md` (add v2.10.0 entry)
|
|
1042
|
+
|
|
1043
|
+
**Step 1: Update package.json version**
|
|
1044
|
+
|
|
1045
|
+
Change `"version": "2.9.1"` to `"version": "2.10.0"`.
|
|
1046
|
+
|
|
1047
|
+
Update description to reflect new tool count: `132 tools` → `142 tools`.
|
|
1048
|
+
|
|
1049
|
+
**Step 2: Add CHANGELOG entry**
|
|
1050
|
+
|
|
1051
|
+
Prepend v2.10.0 entry following the existing format (see v2.9.0 as template):
|
|
1052
|
+
|
|
1053
|
+
```markdown
|
|
1054
|
+
## [2.10.0] — 2026-03-01
|
|
1055
|
+
|
|
1056
|
+
### Added — Direct Social APIs (Tier 6a: Distribution Completeness)
|
|
1057
|
+
|
|
1058
|
+
**LinkedIn Integration (5 MCP tools)**
|
|
1059
|
+
- `li_get_profile` — get authenticated user profile
|
|
1060
|
+
- `li_create_post` — create feed post (text, link, image)
|
|
1061
|
+
- `li_create_article` — publish long-form article
|
|
1062
|
+
- `li_get_analytics` — post analytics (impressions, clicks, engagement)
|
|
1063
|
+
- `li_list_posts` — list recent user posts
|
|
1064
|
+
- New skill: `wp-linkedin` with setup, posting, and analytics references
|
|
1065
|
+
- Detection script: `linkedin_inspect.mjs`
|
|
1066
|
+
|
|
1067
|
+
**Twitter/X Integration (5 MCP tools)**
|
|
1068
|
+
- `tw_create_tweet` — publish tweet (text, media)
|
|
1069
|
+
- `tw_create_thread` — publish connected tweet thread
|
|
1070
|
+
- `tw_get_metrics` — tweet metrics (impressions, likes, retweets)
|
|
1071
|
+
- `tw_list_tweets` — list recent user tweets
|
|
1072
|
+
- `tw_delete_tweet` — delete tweet
|
|
1073
|
+
- New skill: `wp-twitter` with setup, posting, and analytics references
|
|
1074
|
+
- Detection script: `twitter_inspect.mjs`
|
|
1075
|
+
|
|
1076
|
+
**Infrastructure**
|
|
1077
|
+
- LinkedIn client helpers in wordpress.js (hasLinkedIn, makeLinkedInRequest)
|
|
1078
|
+
- Twitter client helpers in wordpress.js (hasTwitter, makeTwitterRequest)
|
|
1079
|
+
- Router v17 (+2 categories: LinkedIn, Twitter/X)
|
|
1080
|
+
- +2 safety hooks (tw_delete_tweet, li_create_article)
|
|
1081
|
+
- Updated wp-distribution-manager agent with LinkedIn + Twitter procedures
|
|
1082
|
+
|
|
1083
|
+
**Stats:** 39 → 41 skills | 132 → 142 MCP tools | 10 → 12 hooks | Router v16 → v17
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
**Step 3: Commit**
|
|
1087
|
+
|
|
1088
|
+
```bash
|
|
1089
|
+
git add package.json CHANGELOG.md
|
|
1090
|
+
git commit -m "chore: bump versione a v2.10.0
|
|
1091
|
+
|
|
1092
|
+
Tier 6a — Direct Social APIs
|
|
1093
|
+
- 10 nuovi MCP tools (5 LinkedIn + 5 Twitter)
|
|
1094
|
+
- 2 nuove skill (wp-linkedin, wp-twitter)
|
|
1095
|
+
- Router v17, 12 hooks"
|
|
1096
|
+
```
|
|
1097
|
+
|
|
1098
|
+
---
|
|
1099
|
+
|
|
1100
|
+
## Release v2.11.0 — Auto-Transform Pipeline
|
|
1101
|
+
|
|
1102
|
+
### Task 12: Add auto-transform references to wp-content-repurposing
|
|
1103
|
+
|
|
1104
|
+
**Files:**
|
|
1105
|
+
- Create: `skills/wp-content-repurposing/references/auto-transform-pipeline.md`
|
|
1106
|
+
- Create: `skills/wp-content-repurposing/references/transform-templates.md`
|
|
1107
|
+
|
|
1108
|
+
**Step 1: Create auto-transform-pipeline.md**
|
|
1109
|
+
|
|
1110
|
+
Content: Pipeline architecture for automated blog→social conversion. Sections: Overview, Pipeline Flow (fetch→extract→template→output), Configuration, Integration Points with `li_create_post`, `tw_create_tweet`, `buf_create_update`.
|
|
1111
|
+
|
|
1112
|
+
**Step 2: Create transform-templates.md**
|
|
1113
|
+
|
|
1114
|
+
Content: Ready-to-use templates for each platform:
|
|
1115
|
+
- Blog → Tweet (280 chars: title + hook + link + hashtags)
|
|
1116
|
+
- Blog → Twitter Thread (split by H2 sections, 1 tweet per section)
|
|
1117
|
+
- Blog → LinkedIn Post (professional tone, key takeaway, link, 1300 chars)
|
|
1118
|
+
- Blog → LinkedIn Article (full content with HTML adaptation)
|
|
1119
|
+
- Blog → Email Snippet (excerpt + read more CTA for Mailchimp/SendGrid)
|
|
1120
|
+
|
|
1121
|
+
Each template includes: format rules, character limits, example input/output, hashtag generation logic.
|
|
1122
|
+
|
|
1123
|
+
**Step 3: Commit**
|
|
1124
|
+
|
|
1125
|
+
```bash
|
|
1126
|
+
git add skills/wp-content-repurposing/references/auto-transform-pipeline.md
|
|
1127
|
+
git add skills/wp-content-repurposing/references/transform-templates.md
|
|
1128
|
+
git commit -m "feat: aggiunge auto-transform pipeline a wp-content-repurposing
|
|
1129
|
+
|
|
1130
|
+
- Pipeline: fetch → extract → template → output
|
|
1131
|
+
- Templates: blog→tweet, blog→thread, blog→LinkedIn post,
|
|
1132
|
+
blog→LinkedIn article, blog→email snippet
|
|
1133
|
+
- Integrazione con li_*, tw_*, buf_*, mc_* tools"
|
|
1134
|
+
```
|
|
1135
|
+
|
|
1136
|
+
---
|
|
1137
|
+
|
|
1138
|
+
### Task 13: Update wp-content-repurposing SKILL.md
|
|
1139
|
+
|
|
1140
|
+
**Files:**
|
|
1141
|
+
- Modify: `skills/wp-content-repurposing/SKILL.md`
|
|
1142
|
+
|
|
1143
|
+
**Step 1: Add auto-transform section to decision tree**
|
|
1144
|
+
|
|
1145
|
+
Add to the decision tree (after line 40): `"auto-transform" / "automatic conversion" / "template pipeline" → Auto-Transform Pipeline (Section 5)`
|
|
1146
|
+
|
|
1147
|
+
**Step 2: Add Section 5**
|
|
1148
|
+
|
|
1149
|
+
After Section 4, add:
|
|
1150
|
+
|
|
1151
|
+
```markdown
|
|
1152
|
+
### Section 5: Auto-Transform Pipeline
|
|
1153
|
+
See `references/auto-transform-pipeline.md`
|
|
1154
|
+
- Automated blog post → multi-platform output
|
|
1155
|
+
- Template system for consistent formatting
|
|
1156
|
+
- Integration with LinkedIn (li_create_post), Twitter (tw_create_tweet, tw_create_thread), Buffer (buf_create_update), Mailchimp (mc_set_campaign_content)
|
|
1157
|
+
- Platform-specific formatting rules and character limits
|
|
1158
|
+
- See `references/transform-templates.md` for ready-to-use templates
|
|
1159
|
+
```
|
|
1160
|
+
|
|
1161
|
+
**Step 3: Update Reference Files table**
|
|
1162
|
+
|
|
1163
|
+
Add two rows for the new references.
|
|
1164
|
+
|
|
1165
|
+
**Step 4: Update Related Skills**
|
|
1166
|
+
|
|
1167
|
+
Add: `- **`wp-linkedin`** — direct LinkedIn publishing for transformed content`
|
|
1168
|
+
Add: `- **`wp-twitter`** — direct Twitter/X publishing for transformed content`
|
|
1169
|
+
|
|
1170
|
+
**Step 5: Commit**
|
|
1171
|
+
|
|
1172
|
+
```bash
|
|
1173
|
+
git add skills/wp-content-repurposing/SKILL.md
|
|
1174
|
+
git commit -m "feat: aggiorna wp-content-repurposing con auto-transform pipeline
|
|
1175
|
+
|
|
1176
|
+
- Nuova Section 5: Auto-Transform Pipeline
|
|
1177
|
+
- Decision tree aggiornato con keywords auto-transform
|
|
1178
|
+
- +2 reference files nella tabella
|
|
1179
|
+
- +2 related skills (wp-linkedin, wp-twitter)"
|
|
1180
|
+
```
|
|
1181
|
+
|
|
1182
|
+
---
|
|
1183
|
+
|
|
1184
|
+
### Task 14: Bump to v2.11.0, update CHANGELOG
|
|
1185
|
+
|
|
1186
|
+
**Files:**
|
|
1187
|
+
- Modify: `package.json` (version: "2.11.0")
|
|
1188
|
+
- Modify: `CHANGELOG.md`
|
|
1189
|
+
|
|
1190
|
+
**Step 1-2:** Bump version to 2.11.0. Add CHANGELOG entry:
|
|
1191
|
+
|
|
1192
|
+
```markdown
|
|
1193
|
+
## [2.11.0] — 2026-03-01
|
|
1194
|
+
|
|
1195
|
+
### Added — Auto-Transform Pipeline (Tier 6b: Distribution Completeness)
|
|
1196
|
+
|
|
1197
|
+
- Auto-transform pipeline in `wp-content-repurposing` skill (new Section 5)
|
|
1198
|
+
- Template system: blog→tweet, blog→thread, blog→LinkedIn post, blog→LinkedIn article, blog→email snippet
|
|
1199
|
+
- Platform-specific formatting rules with character limits
|
|
1200
|
+
- Integration with `li_*`, `tw_*`, `buf_*`, `mc_*` MCP tools
|
|
1201
|
+
- New references: `auto-transform-pipeline.md`, `transform-templates.md`
|
|
1202
|
+
|
|
1203
|
+
**Stats:** 41 skills (unchanged) | 142 MCP tools (unchanged) | WCOP Distribution: 8/10 → 9/10
|
|
1204
|
+
```
|
|
1205
|
+
|
|
1206
|
+
**Step 3: Commit**
|
|
1207
|
+
|
|
1208
|
+
```bash
|
|
1209
|
+
git add package.json CHANGELOG.md
|
|
1210
|
+
git commit -m "chore: bump versione a v2.11.0
|
|
1211
|
+
|
|
1212
|
+
Tier 6b — Auto-Transform Pipeline
|
|
1213
|
+
- Enhancement a wp-content-repurposing con templates
|
|
1214
|
+
- WCOP Distribution layer: 8/10 → 9/10"
|
|
1215
|
+
```
|
|
1216
|
+
|
|
1217
|
+
---
|
|
1218
|
+
|
|
1219
|
+
## Release v2.12.0 — Content Generation + Structured Data
|
|
1220
|
+
|
|
1221
|
+
### Task 15: Create schema.js MCP tool file
|
|
1222
|
+
|
|
1223
|
+
**Files:**
|
|
1224
|
+
- Create: `servers/wp-rest-bridge/build/tools/schema.js`
|
|
1225
|
+
|
|
1226
|
+
**Context:** 3 MCP tools for structured data management. `sd_validate` calls Google Rich Results Test API or validates locally. `sd_inject` updates post meta via WordPress REST API. `sd_list_schemas` scans posts for existing JSON-LD.
|
|
1227
|
+
|
|
1228
|
+
**Step 1: Create the file**
|
|
1229
|
+
|
|
1230
|
+
```javascript
|
|
1231
|
+
import { makeRequest, getActiveSite } from '../wordpress.js';
|
|
1232
|
+
import axios from 'axios';
|
|
1233
|
+
import { z } from 'zod';
|
|
1234
|
+
|
|
1235
|
+
// ── Zod Schemas ─────────────────────────────────────────────────
|
|
1236
|
+
const sdValidateSchema = z.object({
|
|
1237
|
+
url: z.string().optional().describe('URL to validate (fetches and checks JSON-LD)'),
|
|
1238
|
+
markup: z.string().optional().describe('JSON-LD string to validate directly'),
|
|
1239
|
+
}).strict().refine(data => data.url || data.markup, { message: 'Either url or markup is required' });
|
|
1240
|
+
|
|
1241
|
+
const sdInjectSchema = z.object({
|
|
1242
|
+
post_id: z.number().describe('WordPress post/page ID'),
|
|
1243
|
+
schema_type: z.string().describe('Schema.org type (Article, Product, FAQ, HowTo, LocalBusiness, Event, Organization, BreadcrumbList)'),
|
|
1244
|
+
schema_data: z.record(z.any()).describe('Schema.org properties as key-value pairs'),
|
|
1245
|
+
}).strict();
|
|
1246
|
+
|
|
1247
|
+
const sdListSchemasSchema = z.object({
|
|
1248
|
+
schema_type: z.string().optional().describe('Filter by specific Schema.org type'),
|
|
1249
|
+
}).strict();
|
|
1250
|
+
|
|
1251
|
+
// ── Tool Definitions ────────────────────────────────────────────
|
|
1252
|
+
export const schemaTools = [
|
|
1253
|
+
{
|
|
1254
|
+
name: "sd_validate",
|
|
1255
|
+
description: "Validates JSON-LD / Schema.org structured data against Google specs",
|
|
1256
|
+
inputSchema: {
|
|
1257
|
+
type: "object",
|
|
1258
|
+
properties: {
|
|
1259
|
+
url: { type: "string", description: "URL to fetch and validate JSON-LD from" },
|
|
1260
|
+
markup: { type: "string", description: "JSON-LD string to validate directly" },
|
|
1261
|
+
},
|
|
1262
|
+
},
|
|
1263
|
+
},
|
|
1264
|
+
{
|
|
1265
|
+
name: "sd_inject",
|
|
1266
|
+
description: "Injects or updates JSON-LD structured data in a WordPress post/page",
|
|
1267
|
+
inputSchema: {
|
|
1268
|
+
type: "object",
|
|
1269
|
+
properties: {
|
|
1270
|
+
post_id: { type: "number", description: "WordPress post/page ID" },
|
|
1271
|
+
schema_type: { type: "string", description: "Schema.org type (Article, Product, FAQ, etc.)" },
|
|
1272
|
+
schema_data: { type: "object", description: "Schema.org properties as key-value pairs" },
|
|
1273
|
+
},
|
|
1274
|
+
required: ["post_id", "schema_type", "schema_data"],
|
|
1275
|
+
},
|
|
1276
|
+
},
|
|
1277
|
+
{
|
|
1278
|
+
name: "sd_list_schemas",
|
|
1279
|
+
description: "Lists Schema.org types found across the site with counts",
|
|
1280
|
+
inputSchema: {
|
|
1281
|
+
type: "object",
|
|
1282
|
+
properties: {
|
|
1283
|
+
schema_type: { type: "string", description: "Filter by Schema.org type" },
|
|
1284
|
+
},
|
|
1285
|
+
},
|
|
1286
|
+
},
|
|
1287
|
+
];
|
|
1288
|
+
|
|
1289
|
+
// ── Handlers ────────────────────────────────────────────────────
|
|
1290
|
+
export const schemaHandlers = {
|
|
1291
|
+
sd_validate: async (params) => {
|
|
1292
|
+
try {
|
|
1293
|
+
const { url, markup } = params;
|
|
1294
|
+
if (!url && !markup) {
|
|
1295
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "Either url or markup parameter is required." }] } };
|
|
1296
|
+
}
|
|
1297
|
+
let jsonLd;
|
|
1298
|
+
if (markup) {
|
|
1299
|
+
try {
|
|
1300
|
+
jsonLd = JSON.parse(markup);
|
|
1301
|
+
} catch {
|
|
1302
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: "Invalid JSON in markup parameter." }] } };
|
|
1303
|
+
}
|
|
1304
|
+
} else {
|
|
1305
|
+
// Fetch URL and extract JSON-LD
|
|
1306
|
+
const response = await axios.get(url, { timeout: 15000 });
|
|
1307
|
+
const html = response.data;
|
|
1308
|
+
const jsonLdMatch = html.match(/<script[^>]*type="application\/ld\+json"[^>]*>([\s\S]*?)<\/script>/i);
|
|
1309
|
+
if (!jsonLdMatch) {
|
|
1310
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify({ valid: false, error: "No JSON-LD found on page", url }, null, 2) }] } };
|
|
1311
|
+
}
|
|
1312
|
+
try {
|
|
1313
|
+
jsonLd = JSON.parse(jsonLdMatch[1]);
|
|
1314
|
+
} catch {
|
|
1315
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify({ valid: false, error: "Invalid JSON-LD on page", url }, null, 2) }] } };
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// Basic Schema.org validation
|
|
1320
|
+
const issues = [];
|
|
1321
|
+
const schemas = Array.isArray(jsonLd) ? jsonLd : [jsonLd];
|
|
1322
|
+
for (const schema of schemas) {
|
|
1323
|
+
if (!schema['@context'] || !schema['@context'].includes('schema.org')) {
|
|
1324
|
+
issues.push('Missing or invalid @context (should include schema.org)');
|
|
1325
|
+
}
|
|
1326
|
+
if (!schema['@type']) {
|
|
1327
|
+
issues.push('Missing @type');
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
const result = {
|
|
1332
|
+
valid: issues.length === 0,
|
|
1333
|
+
schemas_found: schemas.length,
|
|
1334
|
+
types: schemas.map(s => s['@type']).filter(Boolean),
|
|
1335
|
+
issues: issues.length > 0 ? issues : undefined,
|
|
1336
|
+
source: url || 'inline markup',
|
|
1337
|
+
};
|
|
1338
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] } };
|
|
1339
|
+
} catch (error) {
|
|
1340
|
+
const errorMessage = error.response?.status ? `HTTP ${error.response.status}` : error.message;
|
|
1341
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error validating schema: ${errorMessage}` }] } };
|
|
1342
|
+
}
|
|
1343
|
+
},
|
|
1344
|
+
|
|
1345
|
+
sd_inject: async (params) => {
|
|
1346
|
+
try {
|
|
1347
|
+
const { post_id, schema_type, schema_data } = params;
|
|
1348
|
+
const jsonLd = JSON.stringify({
|
|
1349
|
+
'@context': 'https://schema.org',
|
|
1350
|
+
'@type': schema_type,
|
|
1351
|
+
...schema_data,
|
|
1352
|
+
});
|
|
1353
|
+
// Store JSON-LD in post meta via WordPress REST API
|
|
1354
|
+
const response = await makeRequest('POST', `wp/v2/posts/${post_id}`, {
|
|
1355
|
+
meta: { _schema_json_ld: jsonLd },
|
|
1356
|
+
});
|
|
1357
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify({ success: true, post_id, schema_type, stored: true }, null, 2) }] } };
|
|
1358
|
+
} catch (error) {
|
|
1359
|
+
const errorMessage = error.response?.data?.message || error.message;
|
|
1360
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error injecting schema: ${errorMessage}` }] } };
|
|
1361
|
+
}
|
|
1362
|
+
},
|
|
1363
|
+
|
|
1364
|
+
sd_list_schemas: async (params) => {
|
|
1365
|
+
try {
|
|
1366
|
+
const { schema_type } = params;
|
|
1367
|
+
// Fetch recent posts and check for JSON-LD in meta
|
|
1368
|
+
const posts = await makeRequest('GET', 'wp/v2/posts', { per_page: 100, _fields: 'id,title,meta' });
|
|
1369
|
+
const schemas = {};
|
|
1370
|
+
for (const post of posts) {
|
|
1371
|
+
const meta = post.meta?._schema_json_ld;
|
|
1372
|
+
if (meta) {
|
|
1373
|
+
try {
|
|
1374
|
+
const parsed = JSON.parse(meta);
|
|
1375
|
+
const type = parsed['@type'] || 'Unknown';
|
|
1376
|
+
if (schema_type && type !== schema_type) continue;
|
|
1377
|
+
if (!schemas[type]) schemas[type] = { count: 0, posts: [] };
|
|
1378
|
+
schemas[type].count++;
|
|
1379
|
+
schemas[type].posts.push({ id: post.id, title: post.title?.rendered });
|
|
1380
|
+
} catch { /* skip invalid JSON */ }
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
return { toolResult: { content: [{ type: "text", text: JSON.stringify({ total_types: Object.keys(schemas).length, schemas }, null, 2) }] } };
|
|
1384
|
+
} catch (error) {
|
|
1385
|
+
const errorMessage = error.response?.data?.message || error.message;
|
|
1386
|
+
return { toolResult: { isError: true, content: [{ type: "text", text: `Error listing schemas: ${errorMessage}` }] } };
|
|
1387
|
+
}
|
|
1388
|
+
},
|
|
1389
|
+
};
|
|
1390
|
+
```
|
|
1391
|
+
|
|
1392
|
+
**Step 2: Register in index.js**
|
|
1393
|
+
|
|
1394
|
+
Add import: `import { schemaTools, schemaHandlers } from './schema.js';`
|
|
1395
|
+
Add to allTools: `...schemaTools, // 3 tools`
|
|
1396
|
+
Add to toolHandlers: `...schemaHandlers,`
|
|
1397
|
+
|
|
1398
|
+
**Step 3: Commit**
|
|
1399
|
+
|
|
1400
|
+
```bash
|
|
1401
|
+
git add servers/wp-rest-bridge/build/tools/schema.js servers/wp-rest-bridge/build/tools/index.js
|
|
1402
|
+
git commit -m "feat(bridge): aggiunge schema.js con 3 MCP tools per structured data
|
|
1403
|
+
|
|
1404
|
+
Tools: sd_validate, sd_inject, sd_list_schemas
|
|
1405
|
+
|
|
1406
|
+
- Validazione JSON-LD/Schema.org
|
|
1407
|
+
- Injection in post meta via WordPress REST API
|
|
1408
|
+
- Audit site-wide dei tipi Schema.org presenti
|
|
1409
|
+
- Tool count: 142 → 145"
|
|
1410
|
+
```
|
|
1411
|
+
|
|
1412
|
+
---
|
|
1413
|
+
|
|
1414
|
+
### Task 16: Create wp-structured-data skill
|
|
1415
|
+
|
|
1416
|
+
**Files:**
|
|
1417
|
+
- Create: `skills/wp-structured-data/SKILL.md`
|
|
1418
|
+
- Create: `skills/wp-structured-data/scripts/schema_inspect.mjs`
|
|
1419
|
+
- Create: `skills/wp-structured-data/references/schema-types.md`
|
|
1420
|
+
- Create: `skills/wp-structured-data/references/validation-guide.md`
|
|
1421
|
+
- Create: `skills/wp-structured-data/references/injection-patterns.md`
|
|
1422
|
+
|
|
1423
|
+
**Context:** Same structure as wp-linkedin skill. Detection checks for Yoast/Rank Math plugins. Schema types: Article, Product, LocalBusiness, Event, FAQ, HowTo, Organization, BreadcrumbList.
|
|
1424
|
+
|
|
1425
|
+
**Step 1-4:** Create SKILL.md with triggers ("structured data", "Schema.org", "JSON-LD", "rich snippet"), detection script, and 3 reference files.
|
|
1426
|
+
|
|
1427
|
+
**Step 5: Commit**
|
|
1428
|
+
|
|
1429
|
+
```bash
|
|
1430
|
+
git add skills/wp-structured-data/
|
|
1431
|
+
git commit -m "feat: aggiunge skill wp-structured-data (Schema.org, JSON-LD)
|
|
1432
|
+
|
|
1433
|
+
- SKILL.md con decision tree e 3 MCP tools
|
|
1434
|
+
- schema_inspect.mjs detection script
|
|
1435
|
+
- 3 reference files (schema types, validation, injection patterns)
|
|
1436
|
+
- Tipi supportati: Article, Product, FAQ, HowTo, LocalBusiness, Event"
|
|
1437
|
+
```
|
|
1438
|
+
|
|
1439
|
+
---
|
|
1440
|
+
|
|
1441
|
+
### Task 17: Create wp-content-generation skill
|
|
1442
|
+
|
|
1443
|
+
**Files:**
|
|
1444
|
+
- Create: `skills/wp-content-generation/SKILL.md`
|
|
1445
|
+
- Create: `skills/wp-content-generation/scripts/content_gen_inspect.mjs`
|
|
1446
|
+
- Create: `skills/wp-content-generation/references/generation-workflow.md`
|
|
1447
|
+
- Create: `skills/wp-content-generation/references/brief-templates.md`
|
|
1448
|
+
- Create: `skills/wp-content-generation/references/outline-patterns.md`
|
|
1449
|
+
|
|
1450
|
+
**Context:** This is a procedure-based skill (NO new MCP tools). It guides Claude through: brief→keyword research→outline→draft→SEO optimize→structured data→publish. Uses existing MCP tools (`list_content`, `create_content`, `gsc_*`, `sd_*`).
|
|
1451
|
+
|
|
1452
|
+
**Step 1-4:** Create SKILL.md with the 7-step procedure, detection script (checks wp/v2 access and optional GSC), and 3 reference files.
|
|
1453
|
+
|
|
1454
|
+
**Step 5: Commit**
|
|
1455
|
+
|
|
1456
|
+
```bash
|
|
1457
|
+
git add skills/wp-content-generation/
|
|
1458
|
+
git commit -m "feat: aggiunge skill wp-content-generation (AI content pipeline)
|
|
1459
|
+
|
|
1460
|
+
- Procedure Claude-native: brief → outline → draft → optimize → publish
|
|
1461
|
+
- Nessun nuovo MCP tool (usa wp/v2, gsc_*, sd_* esistenti)
|
|
1462
|
+
- content_gen_inspect.mjs detection script
|
|
1463
|
+
- 3 reference files (workflow, brief templates, outline patterns)"
|
|
1464
|
+
```
|
|
1465
|
+
|
|
1466
|
+
---
|
|
1467
|
+
|
|
1468
|
+
### Task 18: Update router decision-tree.md to v18
|
|
1469
|
+
|
|
1470
|
+
**Files:**
|
|
1471
|
+
- Modify: `skills/wordpress-router/references/decision-tree.md`
|
|
1472
|
+
|
|
1473
|
+
**Step 1: Update header** to v18, add `+ content generation + structured data`.
|
|
1474
|
+
|
|
1475
|
+
**Step 2: Add keywords to operations block**
|
|
1476
|
+
|
|
1477
|
+
Add: `genera contenuto, scrivi post, AI content, content brief, crea articolo, draft post, genera bozza, structured data, Schema.org, JSON-LD, rich snippet, schema markup, dati strutturati`
|
|
1478
|
+
|
|
1479
|
+
**Step 3: Add routing entries to Step 2b**
|
|
1480
|
+
|
|
1481
|
+
```markdown
|
|
1482
|
+
- **genera contenuto / scrivi post / AI content / content brief / crea articolo / draft post / genera bozza**
|
|
1483
|
+
→ `wp-content-generation` skill + `wp-content-strategist` agent
|
|
1484
|
+
- **structured data / Schema.org / JSON-LD / rich snippet / schema markup / dati strutturati**
|
|
1485
|
+
→ `wp-structured-data` skill + `wp-content-strategist` agent
|
|
1486
|
+
```
|
|
1487
|
+
|
|
1488
|
+
**Step 4: Commit**
|
|
1489
|
+
|
|
1490
|
+
```bash
|
|
1491
|
+
git add skills/wordpress-router/references/decision-tree.md
|
|
1492
|
+
git commit -m "feat(router): aggiorna decision-tree a v18
|
|
1493
|
+
|
|
1494
|
+
- +2 categorie: content generation e structured data
|
|
1495
|
+
- 19 → 21 categorie totali nel router"
|
|
1496
|
+
```
|
|
1497
|
+
|
|
1498
|
+
---
|
|
1499
|
+
|
|
1500
|
+
### Task 19: Update wp-content-strategist agent
|
|
1501
|
+
|
|
1502
|
+
**Files:**
|
|
1503
|
+
- Modify: `agents/wp-content-strategist.md`
|
|
1504
|
+
|
|
1505
|
+
**Step 1: Update description** to include AI content generation and structured data management.
|
|
1506
|
+
|
|
1507
|
+
**Step 2: Add example for content generation**
|
|
1508
|
+
|
|
1509
|
+
**Step 3: Add AI Content Generation Procedure** referencing the `wp-content-generation` skill.
|
|
1510
|
+
|
|
1511
|
+
**Step 4: Add Structured Data Procedure** referencing the `wp-structured-data` skill.
|
|
1512
|
+
|
|
1513
|
+
**Step 5: Update Related Skills** with both new skills.
|
|
1514
|
+
|
|
1515
|
+
**Step 6: Commit**
|
|
1516
|
+
|
|
1517
|
+
```bash
|
|
1518
|
+
git add agents/wp-content-strategist.md
|
|
1519
|
+
git commit -m "feat(agent): aggiorna wp-content-strategist per content gen e structured data
|
|
1520
|
+
|
|
1521
|
+
- Descrizione ampliata con AI generation e Schema.org
|
|
1522
|
+
- +2 esempi (content generation, structured data)
|
|
1523
|
+
- +2 procedure (AI content pipeline, schema management)
|
|
1524
|
+
- Aggiorna Related Skills"
|
|
1525
|
+
```
|
|
1526
|
+
|
|
1527
|
+
---
|
|
1528
|
+
|
|
1529
|
+
### Task 20: Bump to v2.12.0, update CHANGELOG
|
|
1530
|
+
|
|
1531
|
+
**Files:**
|
|
1532
|
+
- Modify: `package.json` (version: "2.12.0")
|
|
1533
|
+
- Modify: `CHANGELOG.md`
|
|
1534
|
+
|
|
1535
|
+
**Step 1-2:** Bump version. Add CHANGELOG entry:
|
|
1536
|
+
|
|
1537
|
+
```markdown
|
|
1538
|
+
## [2.12.0] — 2026-03-01
|
|
1539
|
+
|
|
1540
|
+
### Added — Content Generation + Structured Data (Tier 7: Content Factory Completeness)
|
|
1541
|
+
|
|
1542
|
+
**Structured Data (3 MCP tools)**
|
|
1543
|
+
- `sd_validate` — validate JSON-LD/Schema.org markup
|
|
1544
|
+
- `sd_inject` — inject/update JSON-LD in WordPress posts
|
|
1545
|
+
- `sd_list_schemas` — audit Schema.org types across the site
|
|
1546
|
+
- New skill: `wp-structured-data` with schema types, validation, and injection references
|
|
1547
|
+
- Detection script: `schema_inspect.mjs`
|
|
1548
|
+
- Supported types: Article, Product, FAQ, HowTo, LocalBusiness, Event, Organization, BreadcrumbList
|
|
1549
|
+
|
|
1550
|
+
**Content Generation (procedure-based, no new MCP tools)**
|
|
1551
|
+
- New skill: `wp-content-generation` with AI-driven content pipeline
|
|
1552
|
+
- 7-step procedure: brief → keyword research → outline → draft → SEO optimize → structured data → publish
|
|
1553
|
+
- Uses existing MCP tools (wp/v2, gsc_*, sd_*)
|
|
1554
|
+
- Detection script: `content_gen_inspect.mjs`
|
|
1555
|
+
- References: generation workflow, brief templates, outline patterns
|
|
1556
|
+
|
|
1557
|
+
**Infrastructure**
|
|
1558
|
+
- Router v18 (+2 categories: content generation, structured data)
|
|
1559
|
+
- Updated wp-content-strategist agent with AI generation and schema procedures
|
|
1560
|
+
|
|
1561
|
+
**Stats:** 41 → 43 skills | 142 → 145 MCP tools | Router v17 → v18
|
|
1562
|
+
|
|
1563
|
+
### WCOP Score
|
|
1564
|
+
- Content Factory: 9/10 → 10/10 (AI generation + structured data)
|
|
1565
|
+
- Distribution: 9/10 (completed in v2.10-2.11)
|
|
1566
|
+
- **Total: 8.8/10 → 9.2/10**
|
|
1567
|
+
```
|
|
1568
|
+
|
|
1569
|
+
**Step 3: Commit**
|
|
1570
|
+
|
|
1571
|
+
```bash
|
|
1572
|
+
git add package.json CHANGELOG.md
|
|
1573
|
+
git commit -m "chore: bump versione a v2.12.0
|
|
1574
|
+
|
|
1575
|
+
Tier 7 — Content Factory Completeness
|
|
1576
|
+
- 3 MCP tools structured data (sd_*)
|
|
1577
|
+
- 2 nuove skill (wp-content-generation, wp-structured-data)
|
|
1578
|
+
- Router v18, WCOP Score: 8.8 → 9.2/10"
|
|
1579
|
+
```
|
|
1580
|
+
|
|
1581
|
+
---
|
|
1582
|
+
|
|
1583
|
+
## Post-Implementation
|
|
1584
|
+
|
|
1585
|
+
### Task 21: Push and create GitHub release
|
|
1586
|
+
|
|
1587
|
+
```bash
|
|
1588
|
+
git push origin main
|
|
1589
|
+
gh release create v2.12.0 --title "v2.12.0 — Tier 6+7: Distribution + Content Factory" --notes "$(cat <<'EOF'
|
|
1590
|
+
## Tier 6 — Distribution Completeness (v2.10.0 + v2.11.0)
|
|
1591
|
+
|
|
1592
|
+
**LinkedIn Integration (5 tools)**
|
|
1593
|
+
- `li_get_profile`, `li_create_post`, `li_create_article`, `li_get_analytics`, `li_list_posts`
|
|
1594
|
+
- New skill: `wp-linkedin`
|
|
1595
|
+
|
|
1596
|
+
**Twitter/X Integration (5 tools)**
|
|
1597
|
+
- `tw_create_tweet`, `tw_create_thread`, `tw_get_metrics`, `tw_list_tweets`, `tw_delete_tweet`
|
|
1598
|
+
- New skill: `wp-twitter`
|
|
1599
|
+
|
|
1600
|
+
**Auto-Transform Pipeline**
|
|
1601
|
+
- Enhanced `wp-content-repurposing` with template system (blog→tweet, blog→thread, blog→LinkedIn, blog→email)
|
|
1602
|
+
|
|
1603
|
+
## Tier 7 — Content Factory Completeness (v2.12.0)
|
|
1604
|
+
|
|
1605
|
+
**Structured Data (3 tools)**
|
|
1606
|
+
- `sd_validate`, `sd_inject`, `sd_list_schemas`
|
|
1607
|
+
- New skill: `wp-structured-data` (Article, Product, FAQ, HowTo, LocalBusiness, Event)
|
|
1608
|
+
|
|
1609
|
+
**AI Content Generation**
|
|
1610
|
+
- New skill: `wp-content-generation` (brief→outline→draft→optimize→publish)
|
|
1611
|
+
- Claude-native procedure, uses existing REST API and GSC tools
|
|
1612
|
+
|
|
1613
|
+
## Stats
|
|
1614
|
+
- Skills: 39 → 43 (+4 new, +1 enhanced)
|
|
1615
|
+
- MCP Tools: 132 → 145 (+13)
|
|
1616
|
+
- Hooks: 10 → 12
|
|
1617
|
+
- Router: v16 → v18
|
|
1618
|
+
- WCOP Score: 8.8/10 → 9.2/10
|
|
1619
|
+
EOF
|
|
1620
|
+
)"
|
|
1621
|
+
```
|
|
1622
|
+
|
|
1623
|
+
### Task 22: Publish to npm
|
|
1624
|
+
|
|
1625
|
+
```bash
|
|
1626
|
+
npm config set //registry.npmjs.org/:_authToken=<TOKEN>
|
|
1627
|
+
npm publish --access public
|
|
1628
|
+
npm config delete //registry.npmjs.org/:_authToken
|
|
1629
|
+
```
|