myaidev-method 0.0.3 → 0.0.6
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/USER_GUIDE.md +68 -10
- package/bin/cli.js +17 -1
- package/package.json +7 -4
- package/src/mcp/gutenberg-converter.js +447 -0
- package/src/mcp/wordpress-integration.js +11 -3
- package/src/templates/claude/commands/myai-wordpress-publish.md +11 -3
- package/src/templates/docs/WORDPRESS_INTEGRATION.md +143 -0
- package/src/templates/docs/wordpress-api-examples.md +159 -0
- package/src/templates/docs/wordpress-troubleshoot.js +144 -0
package/USER_GUIDE.md
CHANGED
|
@@ -301,23 +301,32 @@ Save as markdown with:
|
|
|
301
301
|
|
|
302
302
|
### Initial Setup
|
|
303
303
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
304
|
+
Use the interactive configuration command to set up WordPress:
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
/myai-configure wordpress
|
|
308
|
+
```
|
|
308
309
|
|
|
309
|
-
|
|
310
|
+
This will guide you through:
|
|
311
|
+
- WordPress site URL
|
|
312
|
+
- Username
|
|
313
|
+
- Application Password creation
|
|
314
|
+
|
|
315
|
+
**Alternative manual setup**:
|
|
316
|
+
1. Create `.env` file in your project root
|
|
317
|
+
2. Add your WordPress credentials:
|
|
310
318
|
```bash
|
|
311
|
-
# Edit .env file
|
|
312
319
|
WORDPRESS_URL=https://your-site.com
|
|
313
320
|
WORDPRESS_USERNAME=your-username
|
|
314
321
|
WORDPRESS_APP_PASSWORD=your-app-password
|
|
315
322
|
```
|
|
316
323
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
324
|
+
### Creating Application Password
|
|
325
|
+
|
|
326
|
+
1. Go to WordPress Admin → Users → Your Profile
|
|
327
|
+
2. Find "Application Passwords" section
|
|
328
|
+
3. Enter name (e.g., "MyAIDev CLI") and click "Add New"
|
|
329
|
+
4. Copy the generated password
|
|
321
330
|
- Copy the generated password
|
|
322
331
|
|
|
323
332
|
### WordPress Commands
|
|
@@ -341,6 +350,55 @@ Save as markdown with:
|
|
|
341
350
|
/myai-content-writer "Blog Post Title" --publish_to_wordpress true
|
|
342
351
|
```
|
|
343
352
|
|
|
353
|
+
### Publishing Workflow
|
|
354
|
+
|
|
355
|
+
**Important**: WordPress publishing works through Claude Code slash commands, not terminal commands.
|
|
356
|
+
|
|
357
|
+
#### To Publish Content:
|
|
358
|
+
|
|
359
|
+
1. **Inside Claude Code** (not terminal):
|
|
360
|
+
```bash
|
|
361
|
+
/myai-wordpress-publish "your-article.md" --status draft
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
2. **The command will**:
|
|
365
|
+
- Read your markdown file
|
|
366
|
+
- Extract frontmatter (title, meta_description, tags, etc.)
|
|
367
|
+
- Connect to WordPress REST API
|
|
368
|
+
- Create/update the post
|
|
369
|
+
- Return post ID and URLs
|
|
370
|
+
|
|
371
|
+
#### Common Publishing Options:
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
# Publish as draft (default)
|
|
375
|
+
/myai-wordpress-publish "article.md"
|
|
376
|
+
|
|
377
|
+
# Publish immediately
|
|
378
|
+
/myai-wordpress-publish "article.md" --status publish
|
|
379
|
+
|
|
380
|
+
# Schedule for review
|
|
381
|
+
/myai-wordpress-publish "article.md" --status pending
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
#### Content Format Expected:
|
|
385
|
+
|
|
386
|
+
Your markdown files should have frontmatter:
|
|
387
|
+
|
|
388
|
+
```markdown
|
|
389
|
+
---
|
|
390
|
+
title: "Your Article Title"
|
|
391
|
+
meta_description: "SEO description for the post"
|
|
392
|
+
slug: "your-article-slug"
|
|
393
|
+
tags: ["tag1", "tag2"]
|
|
394
|
+
category: "Blog"
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
# Your Article Content
|
|
398
|
+
|
|
399
|
+
Content goes here...
|
|
400
|
+
```
|
|
401
|
+
|
|
344
402
|
### WordPress Configuration Options
|
|
345
403
|
|
|
346
404
|
```bash
|
package/bin/cli.js
CHANGED
|
@@ -77,12 +77,13 @@ program
|
|
|
77
77
|
|
|
78
78
|
console.log(chalk.blue('\n🔌 Add WordPress integration (optional):'));
|
|
79
79
|
console.log(chalk.gray(' • Run /myai-configure wordpress for guided setup'));
|
|
80
|
-
console.log(chalk.gray(' • No manual .env file creation needed!'));
|
|
81
80
|
console.log(chalk.gray(' • Publish content with /myai-wordpress-publish filename.md'));
|
|
81
|
+
console.log(chalk.gray(' • Supports both Classic and Gutenberg block editors'));
|
|
82
82
|
|
|
83
83
|
console.log(chalk.magenta('\n📚 Need help?'));
|
|
84
84
|
console.log(chalk.gray(' • Check USER_GUIDE.md for detailed instructions'));
|
|
85
85
|
console.log(chalk.gray(' • Visit: https://github.com/myaione/myaidev-method'));
|
|
86
|
+
console.log(chalk.gray(' • WordPress docs: GitHub repo > docs/'));
|
|
86
87
|
}
|
|
87
88
|
console.log(chalk.cyan(`\n🔄 Restart ${cliType} to load your new AI-powered commands!`));
|
|
88
89
|
|
|
@@ -204,6 +205,21 @@ DEFAULT_TONE=professional
|
|
|
204
205
|
if (await fs.pathExists(wordpressMcpServer)) {
|
|
205
206
|
await fs.copy(wordpressMcpServer, path.join(mcpDir, 'wordpress-server.js'));
|
|
206
207
|
}
|
|
208
|
+
|
|
209
|
+
// Copy Gutenberg converter
|
|
210
|
+
const gutenbergConverter = path.join(__dirname, '..', 'src', 'mcp', 'gutenberg-converter.js');
|
|
211
|
+
if (await fs.pathExists(gutenbergConverter)) {
|
|
212
|
+
await fs.copy(gutenbergConverter, path.join(mcpDir, 'gutenberg-converter.js'));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Copy USER_GUIDE.md - Essential for users to understand customization
|
|
216
|
+
const userGuide = path.join(__dirname, '..', 'USER_GUIDE.md');
|
|
217
|
+
if (await fs.pathExists(userGuide)) {
|
|
218
|
+
await fs.copy(userGuide, path.join(projectDir, 'USER_GUIDE.md'));
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Note: WORDPRESS_INTEGRATION.md could be helpful, but keeping docs minimal
|
|
222
|
+
// Users can find detailed docs on GitHub if needed
|
|
207
223
|
}
|
|
208
224
|
|
|
209
225
|
async function setupGemini(projectDir) {
|
package/package.json
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myaidev-method",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "AI CLI tools package with custom subagents and MCP integrations for Claude Code, Gemini CLI, and more",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"myaidev-method": "./bin/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"test": "
|
|
11
|
-
"
|
|
12
|
-
"
|
|
10
|
+
"test": "node test/run-tests.js",
|
|
11
|
+
"test:gutenberg": "node test/test-gutenberg-converter.js",
|
|
12
|
+
"test:install": "node test/test-installation.js",
|
|
13
|
+
"prepublishOnly": "npm install && npm test",
|
|
14
|
+
"postinstall": "echo \"MyAIDev Method installed successfully! Run 'npx myaidev-method init --claude' to get started.\"",
|
|
15
|
+
"wordpress:troubleshoot": "node src/templates/docs/wordpress-troubleshoot.js"
|
|
13
16
|
},
|
|
14
17
|
"keywords": [
|
|
15
18
|
"claude-code",
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gutenberg Block Converter
|
|
3
|
+
* Converts HTML/Markdown content to WordPress Gutenberg block format
|
|
4
|
+
* Following WordPress Block Editor Handbook specifications
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export class GutenbergConverter {
|
|
8
|
+
/**
|
|
9
|
+
* Convert HTML content to Gutenberg blocks
|
|
10
|
+
* @param {string} html - HTML content to convert
|
|
11
|
+
* @returns {string} - Gutenberg block formatted content
|
|
12
|
+
*/
|
|
13
|
+
static htmlToGutenberg(html) {
|
|
14
|
+
// Parse HTML and convert to blocks
|
|
15
|
+
let gutenbergContent = '';
|
|
16
|
+
|
|
17
|
+
// Split content into sections for processing
|
|
18
|
+
const sections = this.parseHTMLSections(html);
|
|
19
|
+
|
|
20
|
+
sections.forEach(section => {
|
|
21
|
+
const block = this.createBlock(section);
|
|
22
|
+
if (block) {
|
|
23
|
+
gutenbergContent += block + '\n\n';
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return gutenbergContent.trim();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Parse HTML into sections for block conversion
|
|
32
|
+
*/
|
|
33
|
+
static parseHTMLSections(html) {
|
|
34
|
+
const sections = [];
|
|
35
|
+
|
|
36
|
+
// Remove excess whitespace and normalize
|
|
37
|
+
const normalizedHtml = html.replace(/\n\s*\n/g, '\n').trim();
|
|
38
|
+
|
|
39
|
+
// Regular expressions for different HTML elements
|
|
40
|
+
const patterns = {
|
|
41
|
+
heading: /<h([1-6])(?:\s[^>]*)?>(.+?)<\/h\1>/gi,
|
|
42
|
+
paragraph: /<p(?:\s[^>]*)?>(.+?)<\/p>/gi,
|
|
43
|
+
list: /<(ul|ol)(?:\s[^>]*)?>(.+?)<\/\1>/gis,
|
|
44
|
+
blockquote: /<blockquote(?:\s[^>]*)?>(.+?)<\/blockquote>/gis,
|
|
45
|
+
pre: /<pre(?:\s[^>]*)?><code(?:\s[^>]*)?>(.+?)<\/code><\/pre>/gis,
|
|
46
|
+
image: /<img\s+([^>]+)>/gi,
|
|
47
|
+
hr: /<hr(?:\s[^>]*)?>/gi
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Process the HTML string sequentially
|
|
51
|
+
let lastIndex = 0;
|
|
52
|
+
const processedParts = [];
|
|
53
|
+
|
|
54
|
+
// Create a combined pattern to find all blocks
|
|
55
|
+
const combinedPattern = new RegExp(
|
|
56
|
+
'(' +
|
|
57
|
+
'<h[1-6](?:\\s[^>]*)?>.*?</h[1-6]>|' +
|
|
58
|
+
'<p(?:\\s[^>]*)?>.*?</p>|' +
|
|
59
|
+
'<(?:ul|ol)(?:\\s[^>]*)?>.*?</(?:ul|ol)>|' +
|
|
60
|
+
'<blockquote(?:\\s[^>]*)?>.*?</blockquote>|' +
|
|
61
|
+
'<pre(?:\\s[^>]*)?><code(?:\\s[^>]*)?>.*?</code></pre>|' +
|
|
62
|
+
'<img\\s+[^>]+>|' +
|
|
63
|
+
'<hr(?:\\s[^>]*)?>' +
|
|
64
|
+
')',
|
|
65
|
+
'gis'
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
let match;
|
|
69
|
+
while ((match = combinedPattern.exec(normalizedHtml)) !== null) {
|
|
70
|
+
// Add any text between matches as a paragraph
|
|
71
|
+
if (match.index > lastIndex) {
|
|
72
|
+
const text = normalizedHtml.substring(lastIndex, match.index).trim();
|
|
73
|
+
if (text && !text.match(/^\s*$/)) {
|
|
74
|
+
sections.push({ type: 'paragraph', content: this.stripTags(text) });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const fullMatch = match[0];
|
|
79
|
+
|
|
80
|
+
// Determine block type and extract content
|
|
81
|
+
if (fullMatch.match(/<h([1-6])/i)) {
|
|
82
|
+
const level = fullMatch.match(/<h([1-6])/i)[1];
|
|
83
|
+
const content = fullMatch.replace(/<\/?h[1-6](?:\s[^>]*)?>/gi, '');
|
|
84
|
+
sections.push({ type: 'heading', level: parseInt(level), content: this.stripTags(content) });
|
|
85
|
+
}
|
|
86
|
+
else if (fullMatch.match(/<p(?:\s|>)/i)) {
|
|
87
|
+
const content = fullMatch.replace(/<\/?p(?:\s[^>]*)?>/gi, '');
|
|
88
|
+
sections.push({ type: 'paragraph', content: this.stripTags(content) });
|
|
89
|
+
}
|
|
90
|
+
else if (fullMatch.match(/<(ul|ol)/i)) {
|
|
91
|
+
const listType = fullMatch.match(/<(ul|ol)/i)[1];
|
|
92
|
+
const items = this.parseListItems(fullMatch);
|
|
93
|
+
sections.push({ type: 'list', ordered: listType === 'ol', items });
|
|
94
|
+
}
|
|
95
|
+
else if (fullMatch.match(/<blockquote/i)) {
|
|
96
|
+
const content = fullMatch.replace(/<\/?blockquote(?:\s[^>]*)?>/gi, '');
|
|
97
|
+
sections.push({ type: 'quote', content: this.stripTags(content) });
|
|
98
|
+
}
|
|
99
|
+
else if (fullMatch.match(/<pre/i)) {
|
|
100
|
+
const content = fullMatch.replace(/<\/?(?:pre|code)(?:\s[^>]*)?>/gi, '');
|
|
101
|
+
sections.push({ type: 'code', content: this.decodeHtml(content) });
|
|
102
|
+
}
|
|
103
|
+
else if (fullMatch.match(/<img/i)) {
|
|
104
|
+
const attrs = this.parseImageAttributes(fullMatch);
|
|
105
|
+
sections.push({ type: 'image', ...attrs });
|
|
106
|
+
}
|
|
107
|
+
else if (fullMatch.match(/<hr/i)) {
|
|
108
|
+
sections.push({ type: 'separator' });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
lastIndex = match.index + fullMatch.length;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Add any remaining content
|
|
115
|
+
if (lastIndex < normalizedHtml.length) {
|
|
116
|
+
const text = normalizedHtml.substring(lastIndex).trim();
|
|
117
|
+
if (text && !text.match(/^\s*$/)) {
|
|
118
|
+
sections.push({ type: 'paragraph', content: this.stripTags(text) });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return sections;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Parse list items from HTML list
|
|
127
|
+
*/
|
|
128
|
+
static parseListItems(listHtml) {
|
|
129
|
+
const items = [];
|
|
130
|
+
const itemPattern = /<li(?:\s[^>]*)?>(.+?)<\/li>/gis;
|
|
131
|
+
let match;
|
|
132
|
+
|
|
133
|
+
while ((match = itemPattern.exec(listHtml)) !== null) {
|
|
134
|
+
items.push(this.stripTags(match[1].trim()));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return items;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Parse image attributes from img tag
|
|
142
|
+
*/
|
|
143
|
+
static parseImageAttributes(imgTag) {
|
|
144
|
+
const attrs = {};
|
|
145
|
+
|
|
146
|
+
// Extract src
|
|
147
|
+
const srcMatch = imgTag.match(/src=["']([^"']+)["']/i);
|
|
148
|
+
if (srcMatch) attrs.url = srcMatch[1];
|
|
149
|
+
|
|
150
|
+
// Extract alt text
|
|
151
|
+
const altMatch = imgTag.match(/alt=["']([^"']+)["']/i);
|
|
152
|
+
if (altMatch) attrs.alt = altMatch[1];
|
|
153
|
+
|
|
154
|
+
// Extract title
|
|
155
|
+
const titleMatch = imgTag.match(/title=["']([^"']+)["']/i);
|
|
156
|
+
if (titleMatch) attrs.caption = titleMatch[1];
|
|
157
|
+
|
|
158
|
+
return attrs;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Create a Gutenberg block from a section
|
|
163
|
+
*/
|
|
164
|
+
static createBlock(section) {
|
|
165
|
+
switch (section.type) {
|
|
166
|
+
case 'heading':
|
|
167
|
+
return this.createHeadingBlock(section.level, section.content);
|
|
168
|
+
|
|
169
|
+
case 'paragraph':
|
|
170
|
+
return this.createParagraphBlock(section.content);
|
|
171
|
+
|
|
172
|
+
case 'list':
|
|
173
|
+
return this.createListBlock(section.items, section.ordered);
|
|
174
|
+
|
|
175
|
+
case 'quote':
|
|
176
|
+
return this.createQuoteBlock(section.content);
|
|
177
|
+
|
|
178
|
+
case 'code':
|
|
179
|
+
return this.createCodeBlock(section.content);
|
|
180
|
+
|
|
181
|
+
case 'image':
|
|
182
|
+
return this.createImageBlock(section.url, section.alt, section.caption);
|
|
183
|
+
|
|
184
|
+
case 'separator':
|
|
185
|
+
return this.createSeparatorBlock();
|
|
186
|
+
|
|
187
|
+
default:
|
|
188
|
+
return this.createParagraphBlock(section.content || '');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Create heading block
|
|
194
|
+
*/
|
|
195
|
+
static createHeadingBlock(level, content) {
|
|
196
|
+
return `<!-- wp:heading {"level":${level}} -->
|
|
197
|
+
<h${level} class="wp-block-heading">${this.escapeHtml(content)}</h${level}>
|
|
198
|
+
<!-- /wp:heading -->`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Create paragraph block
|
|
203
|
+
*/
|
|
204
|
+
static createParagraphBlock(content) {
|
|
205
|
+
// Handle empty paragraphs
|
|
206
|
+
if (!content || content.trim() === '') {
|
|
207
|
+
return '';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return `<!-- wp:paragraph -->
|
|
211
|
+
<p>${this.escapeHtml(content)}</p>
|
|
212
|
+
<!-- /wp:paragraph -->`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Create list block
|
|
217
|
+
*/
|
|
218
|
+
static createListBlock(items, ordered = false) {
|
|
219
|
+
const tag = ordered ? 'ol' : 'ul';
|
|
220
|
+
const blockName = ordered ? 'list' : 'list';
|
|
221
|
+
const listItems = items.map(item => `<li>${this.escapeHtml(item)}</li>`).join('\n');
|
|
222
|
+
|
|
223
|
+
const attributes = ordered ? ' {"ordered":true}' : '';
|
|
224
|
+
|
|
225
|
+
return `<!-- wp:list${attributes} -->
|
|
226
|
+
<${tag} class="wp-block-list">${listItems}</${tag}>
|
|
227
|
+
<!-- /wp:list -->`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Create quote block
|
|
232
|
+
*/
|
|
233
|
+
static createQuoteBlock(content) {
|
|
234
|
+
return `<!-- wp:quote -->
|
|
235
|
+
<blockquote class="wp-block-quote">
|
|
236
|
+
<p>${this.escapeHtml(content)}</p>
|
|
237
|
+
</blockquote>
|
|
238
|
+
<!-- /wp:quote -->`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Create code block
|
|
243
|
+
*/
|
|
244
|
+
static createCodeBlock(code) {
|
|
245
|
+
// Escape the code content for HTML
|
|
246
|
+
const escapedCode = this.escapeHtml(code);
|
|
247
|
+
|
|
248
|
+
return `<!-- wp:code -->
|
|
249
|
+
<pre class="wp-block-code"><code>${escapedCode}</code></pre>
|
|
250
|
+
<!-- /wp:code -->`;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Create image block
|
|
255
|
+
*/
|
|
256
|
+
static createImageBlock(url, alt = '', caption = '') {
|
|
257
|
+
let attributes = {};
|
|
258
|
+
if (alt) attributes.alt = alt;
|
|
259
|
+
|
|
260
|
+
const attributesJson = Object.keys(attributes).length > 0
|
|
261
|
+
? ' ' + JSON.stringify(attributes)
|
|
262
|
+
: '';
|
|
263
|
+
|
|
264
|
+
let imageHtml = `<!-- wp:image${attributesJson} -->
|
|
265
|
+
<figure class="wp-block-image"><img src="${url}"${alt ? ` alt="${this.escapeHtml(alt)}"` : ''}/>`;
|
|
266
|
+
|
|
267
|
+
if (caption) {
|
|
268
|
+
imageHtml += `<figcaption class="wp-element-caption">${this.escapeHtml(caption)}</figcaption>`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
imageHtml += `</figure>
|
|
272
|
+
<!-- /wp:image -->`;
|
|
273
|
+
|
|
274
|
+
return imageHtml;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Create separator block
|
|
279
|
+
*/
|
|
280
|
+
static createSeparatorBlock() {
|
|
281
|
+
return `<!-- wp:separator -->
|
|
282
|
+
<hr class="wp-block-separator has-alpha-channel-opacity"/>
|
|
283
|
+
<!-- /wp:separator -->`;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Create columns block for advanced layouts
|
|
288
|
+
*/
|
|
289
|
+
static createColumnsBlock(columns) {
|
|
290
|
+
const columnCount = columns.length;
|
|
291
|
+
let columnsHtml = `<!-- wp:columns {"columns":${columnCount}} -->\n<div class="wp-block-columns">`;
|
|
292
|
+
|
|
293
|
+
columns.forEach(column => {
|
|
294
|
+
columnsHtml += `\n<!-- wp:column -->\n<div class="wp-block-column">`;
|
|
295
|
+
columnsHtml += `\n${column}`;
|
|
296
|
+
columnsHtml += `\n</div>\n<!-- /wp:column -->`;
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
columnsHtml += `\n</div>\n<!-- /wp:columns -->`;
|
|
300
|
+
return columnsHtml;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Create button block
|
|
305
|
+
*/
|
|
306
|
+
static createButtonBlock(text, url = '#', align = 'none') {
|
|
307
|
+
return `<!-- wp:buttons {"layout":{"type":"flex","justifyContent":"${align}"}} -->
|
|
308
|
+
<div class="wp-block-buttons">
|
|
309
|
+
<!-- wp:button -->
|
|
310
|
+
<div class="wp-block-button"><a class="wp-block-button__link wp-element-button" href="${url}">${this.escapeHtml(text)}</a></div>
|
|
311
|
+
<!-- /wp:button -->
|
|
312
|
+
</div>
|
|
313
|
+
<!-- /wp:buttons -->`;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Create table block
|
|
318
|
+
*/
|
|
319
|
+
static createTableBlock(headers, rows) {
|
|
320
|
+
let tableHtml = `<!-- wp:table -->
|
|
321
|
+
<figure class="wp-block-table"><table class="wp-block-table">`;
|
|
322
|
+
|
|
323
|
+
// Add headers
|
|
324
|
+
if (headers && headers.length > 0) {
|
|
325
|
+
tableHtml += '\n<thead>\n<tr>';
|
|
326
|
+
headers.forEach(header => {
|
|
327
|
+
tableHtml += `<th>${this.escapeHtml(header)}</th>`;
|
|
328
|
+
});
|
|
329
|
+
tableHtml += '</tr>\n</thead>';
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Add rows
|
|
333
|
+
tableHtml += '\n<tbody>';
|
|
334
|
+
rows.forEach(row => {
|
|
335
|
+
tableHtml += '\n<tr>';
|
|
336
|
+
row.forEach(cell => {
|
|
337
|
+
tableHtml += `<td>${this.escapeHtml(cell)}</td>`;
|
|
338
|
+
});
|
|
339
|
+
tableHtml += '</tr>';
|
|
340
|
+
});
|
|
341
|
+
tableHtml += '\n</tbody>';
|
|
342
|
+
|
|
343
|
+
tableHtml += `\n</table></figure>
|
|
344
|
+
<!-- /wp:table -->`;
|
|
345
|
+
|
|
346
|
+
return tableHtml;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Utility: Strip HTML tags from text
|
|
351
|
+
*/
|
|
352
|
+
static stripTags(text) {
|
|
353
|
+
return text.replace(/<[^>]+>/g, '').trim();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Utility: Escape HTML special characters
|
|
358
|
+
*/
|
|
359
|
+
static escapeHtml(text) {
|
|
360
|
+
const map = {
|
|
361
|
+
'&': '&',
|
|
362
|
+
'<': '<',
|
|
363
|
+
'>': '>',
|
|
364
|
+
'"': '"',
|
|
365
|
+
"'": ''',
|
|
366
|
+
"/": '/'
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
return text.replace(/[&<>"'/]/g, char => map[char]);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Utility: Decode HTML entities
|
|
374
|
+
*/
|
|
375
|
+
static decodeHtml(text) {
|
|
376
|
+
const entities = {
|
|
377
|
+
'&': '&',
|
|
378
|
+
'<': '<',
|
|
379
|
+
'>': '>',
|
|
380
|
+
'"': '"',
|
|
381
|
+
''': "'",
|
|
382
|
+
'/': '/',
|
|
383
|
+
''': "'",
|
|
384
|
+
' ': ' '
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
return text.replace(/&[#a-z0-9]+;/gi, entity => entities[entity] || entity);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Convert markdown to Gutenberg blocks (bonus feature)
|
|
392
|
+
*/
|
|
393
|
+
static markdownToGutenberg(markdown) {
|
|
394
|
+
// First convert markdown to HTML (simplified version)
|
|
395
|
+
let html = markdown
|
|
396
|
+
// Headers
|
|
397
|
+
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
|
|
398
|
+
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
|
|
399
|
+
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
|
|
400
|
+
// Bold
|
|
401
|
+
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
|
402
|
+
// Italic
|
|
403
|
+
.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
|
404
|
+
// Code blocks
|
|
405
|
+
.replace(/```[\s\S]*?```/g, match => {
|
|
406
|
+
const code = match.slice(3, -3).trim();
|
|
407
|
+
return `<pre><code>${code}</code></pre>`;
|
|
408
|
+
})
|
|
409
|
+
// Inline code
|
|
410
|
+
.replace(/`(.+?)`/g, '<code>$1</code>')
|
|
411
|
+
// Blockquotes
|
|
412
|
+
.replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>')
|
|
413
|
+
// Horizontal rules
|
|
414
|
+
.replace(/^---$/gm, '<hr>')
|
|
415
|
+
// Lists (simplified)
|
|
416
|
+
.replace(/^- (.+)$/gm, '<li>$1</li>')
|
|
417
|
+
.replace(/^(\d+)\. (.+)$/gm, '<li>$2</li>');
|
|
418
|
+
|
|
419
|
+
// Wrap consecutive li elements in ul/ol
|
|
420
|
+
html = html.replace(/(<li>.*<\/li>\n?)+/g, match => {
|
|
421
|
+
return `<ul>${match}</ul>`;
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
// Convert paragraphs
|
|
425
|
+
const lines = html.split('\n');
|
|
426
|
+
const processedLines = [];
|
|
427
|
+
let inBlock = false;
|
|
428
|
+
|
|
429
|
+
lines.forEach(line => {
|
|
430
|
+
const trimmed = line.trim();
|
|
431
|
+
if (trimmed === '') {
|
|
432
|
+
inBlock = false;
|
|
433
|
+
} else if (!trimmed.startsWith('<')) {
|
|
434
|
+
processedLines.push(`<p>${trimmed}</p>`);
|
|
435
|
+
} else {
|
|
436
|
+
processedLines.push(trimmed);
|
|
437
|
+
}
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
html = processedLines.join('\n');
|
|
441
|
+
|
|
442
|
+
// Now convert HTML to Gutenberg
|
|
443
|
+
return this.htmlToGutenberg(html);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export default GutenbergConverter;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fetch from 'node-fetch';
|
|
2
|
+
import { GutenbergConverter } from './gutenberg-converter.js';
|
|
2
3
|
|
|
3
4
|
export class WordPressMCP {
|
|
4
5
|
constructor(config) {
|
|
@@ -32,9 +33,15 @@ export class WordPressMCP {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
async createPost(params) {
|
|
36
|
+
// Convert content to Gutenberg format if requested
|
|
37
|
+
let content = params.content;
|
|
38
|
+
if (params.use_gutenberg === true || params.gutenberg === true) {
|
|
39
|
+
content = GutenbergConverter.htmlToGutenberg(params.content);
|
|
40
|
+
}
|
|
41
|
+
|
|
35
42
|
const postData = {
|
|
36
43
|
title: params.title,
|
|
37
|
-
content:
|
|
44
|
+
content: content,
|
|
38
45
|
status: params.status || this.defaults.post_status,
|
|
39
46
|
categories: params.categories || [],
|
|
40
47
|
tags: params.tags || [],
|
|
@@ -191,9 +198,10 @@ export class WordPressMCP {
|
|
|
191
198
|
content: contentData.content,
|
|
192
199
|
excerpt: contentData.meta_description,
|
|
193
200
|
slug: contentData.slug,
|
|
194
|
-
status: 'draft', //
|
|
201
|
+
status: contentData.status || 'draft', // Default to draft for review
|
|
195
202
|
tags: tags,
|
|
196
|
-
categories: categoryId ? [categoryId] : []
|
|
203
|
+
categories: categoryId ? [categoryId] : [],
|
|
204
|
+
use_gutenberg: contentData.use_gutenberg || false // Pass Gutenberg option
|
|
197
205
|
};
|
|
198
206
|
|
|
199
207
|
// Create the post
|
|
@@ -18,21 +18,29 @@ Read and publish the file specified in $ARGUMENTS to WordPress.
|
|
|
18
18
|
- slug
|
|
19
19
|
- tags
|
|
20
20
|
- category
|
|
21
|
+
- use_gutenberg (optional, default: false)
|
|
21
22
|
3. **Check WordPress configuration** in environment variables:
|
|
22
23
|
- WORDPRESS_URL
|
|
23
24
|
- WORDPRESS_USERNAME
|
|
24
25
|
- WORDPRESS_APP_PASSWORD
|
|
26
|
+
- WORDPRESS_USE_GUTENBERG (optional, default: false)
|
|
25
27
|
4. **Connect to WordPress** using the REST API
|
|
26
|
-
5. **
|
|
27
|
-
|
|
28
|
+
5. **Convert content format** if Gutenberg is enabled:
|
|
29
|
+
- If use_gutenberg is true or WORDPRESS_USE_GUTENBERG is true
|
|
30
|
+
- Convert HTML to Gutenberg block format
|
|
31
|
+
6. **Create or update the post** with status (default: draft)
|
|
32
|
+
7. **Report the result** including:
|
|
28
33
|
- Post ID
|
|
29
34
|
- Preview URL
|
|
30
35
|
- Edit URL in WordPress admin
|
|
36
|
+
- Format used (Classic or Gutenberg)
|
|
31
37
|
|
|
32
38
|
## Parameters
|
|
33
39
|
|
|
34
40
|
- File path (required): Path to markdown file
|
|
35
|
-
-
|
|
41
|
+
- --status: draft (default), publish, pending, private
|
|
42
|
+
- --gutenberg: Use Gutenberg block format (default: false)
|
|
43
|
+
- --classic: Force classic editor format (overrides defaults)
|
|
36
44
|
|
|
37
45
|
## Error Handling
|
|
38
46
|
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# WordPress Integration - How It Works
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The MyAIDev Method package provides WordPress integration through Claude Code slash commands, not terminal npm scripts. This is the correct design for Claude Code packages.
|
|
6
|
+
|
|
7
|
+
## Publishing Workflow
|
|
8
|
+
|
|
9
|
+
### ❌ Incorrect Usage (What You Tried)
|
|
10
|
+
```bash
|
|
11
|
+
# This doesn't work - there's no npm script
|
|
12
|
+
npm run wordpress:publish -- --file lovable-to-ultrastack-deployment.md --status draft
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### ✅ Correct Usage (Inside Claude Code)
|
|
16
|
+
```bash
|
|
17
|
+
# Use this slash command inside Claude Code
|
|
18
|
+
/myai-wordpress-publish "lovable-to-ultrastack-deployment.md" --status draft
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## How It Works
|
|
22
|
+
|
|
23
|
+
1. **Command Definition**: `.claude/commands/myai-wordpress-publish.md`
|
|
24
|
+
- Defines the slash command interface
|
|
25
|
+
- Specifies parameters and expected behavior
|
|
26
|
+
- Uses Claude Code's built-in tools (Read, Task, WebFetch)
|
|
27
|
+
|
|
28
|
+
2. **MCP Integration**: `.claude/mcp/wordpress-server.js`
|
|
29
|
+
- Provides WordPress REST API integration
|
|
30
|
+
- Handles authentication via Application Passwords
|
|
31
|
+
- Manages posts, categories, tags, and media
|
|
32
|
+
|
|
33
|
+
3. **Configuration**: Environment variables or `.env` file
|
|
34
|
+
- `WORDPRESS_URL`: Your site URL
|
|
35
|
+
- `WORDPRESS_USERNAME`: WordPress username
|
|
36
|
+
- `WORDPRESS_APP_PASSWORD`: Application password
|
|
37
|
+
|
|
38
|
+
## Step-by-Step Publishing Process
|
|
39
|
+
|
|
40
|
+
### 1. Setup WordPress Credentials (One Time)
|
|
41
|
+
Inside Claude Code:
|
|
42
|
+
```bash
|
|
43
|
+
/myai-configure wordpress
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This creates a `.env` file with:
|
|
47
|
+
```
|
|
48
|
+
WORDPRESS_URL=https://your-site.com
|
|
49
|
+
WORDPRESS_USERNAME=your-username
|
|
50
|
+
WORDPRESS_APP_PASSWORD=your-app-password
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Prepare Your Content
|
|
54
|
+
Create a markdown file with frontmatter:
|
|
55
|
+
```markdown
|
|
56
|
+
---
|
|
57
|
+
title: "Your Article Title"
|
|
58
|
+
meta_description: "SEO description"
|
|
59
|
+
slug: "your-article-slug"
|
|
60
|
+
tags: ["web-development", "tutorial"]
|
|
61
|
+
category: "Blog"
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
# Your Article Content
|
|
65
|
+
|
|
66
|
+
Content goes here...
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Publish to WordPress
|
|
70
|
+
Inside Claude Code:
|
|
71
|
+
```bash
|
|
72
|
+
/myai-wordpress-publish "your-file.md" --status draft
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 4. What Happens Next
|
|
76
|
+
The command will:
|
|
77
|
+
- Read your markdown file
|
|
78
|
+
- Parse the frontmatter
|
|
79
|
+
- Connect to WordPress REST API
|
|
80
|
+
- Create categories/tags if needed
|
|
81
|
+
- Create the post as a draft
|
|
82
|
+
- Return post ID and URLs
|
|
83
|
+
|
|
84
|
+
## Expected Output
|
|
85
|
+
|
|
86
|
+
```
|
|
87
|
+
✅ Successfully published to WordPress!
|
|
88
|
+
- Post ID: 123
|
|
89
|
+
- Status: draft
|
|
90
|
+
- Preview: https://your-site.com/?p=123&preview=true
|
|
91
|
+
- Edit: https://your-site.com/wp-admin/post.php?post=123&action=edit
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## WordPress Application Password Setup
|
|
95
|
+
|
|
96
|
+
1. Go to your WordPress Admin → Users → Your Profile
|
|
97
|
+
2. Scroll to "Application Passwords" section
|
|
98
|
+
3. Enter name: "MyAIDev CLI"
|
|
99
|
+
4. Click "Add New"
|
|
100
|
+
5. Copy the generated password (save it!)
|
|
101
|
+
6. Use this password in your `.env` file
|
|
102
|
+
|
|
103
|
+
## Why No npm Scripts?
|
|
104
|
+
|
|
105
|
+
This is a **Claude Code package**, not a traditional npm package with CLI scripts. The functionality is designed to work within Claude Code's environment using:
|
|
106
|
+
|
|
107
|
+
- Slash commands (like `/myai-wordpress-publish`)
|
|
108
|
+
- Claude Code's built-in tools (Read, Write, WebFetch, etc.)
|
|
109
|
+
- MCP (Model Context Protocol) integrations
|
|
110
|
+
- Environment variable access
|
|
111
|
+
|
|
112
|
+
This design provides better integration with Claude Code's AI capabilities and workflow.
|
|
113
|
+
|
|
114
|
+
## Troubleshooting
|
|
115
|
+
|
|
116
|
+
### "package.json not found" Error
|
|
117
|
+
This happens when trying to use `npm run` commands. Use slash commands instead.
|
|
118
|
+
|
|
119
|
+
### "WordPress credentials not configured"
|
|
120
|
+
Run `/myai-configure wordpress` to set up credentials.
|
|
121
|
+
|
|
122
|
+
### "File not found" Error
|
|
123
|
+
Check the file path is correct relative to your current directory.
|
|
124
|
+
|
|
125
|
+
### Authentication Errors
|
|
126
|
+
Verify your Application Password is correct and has proper permissions.
|
|
127
|
+
|
|
128
|
+
## Development Notes
|
|
129
|
+
|
|
130
|
+
If you want to add npm scripts for WordPress publishing, you would need to:
|
|
131
|
+
|
|
132
|
+
1. Add scripts to `package.json`:
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"scripts": {
|
|
136
|
+
"wordpress:publish": "node scripts/wordpress-publish.js"
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
2. Create `scripts/wordpress-publish.js` that uses the MCP integration
|
|
142
|
+
|
|
143
|
+
However, this goes against the Claude Code design pattern. The slash command approach is the intended and recommended method.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# WordPress REST API Testing Examples
|
|
2
|
+
|
|
3
|
+
## Your Credentials
|
|
4
|
+
- **Username:** admin
|
|
5
|
+
- **Application Password:** iqMC L5Eh JexQ 0Ior BJIo pLH3
|
|
6
|
+
- **WordPress URL:** [You need to provide this]
|
|
7
|
+
|
|
8
|
+
## Basic Authentication Test
|
|
9
|
+
|
|
10
|
+
### Using curl (Terminal)
|
|
11
|
+
```bash
|
|
12
|
+
# Replace YOUR_SITE_URL with your actual WordPress URL
|
|
13
|
+
curl -X GET "YOUR_SITE_URL/wp-json/wp/v2/posts?per_page=1" \
|
|
14
|
+
-H "Authorization: Basic $(echo -n 'admin:iqMC L5Eh JexQ 0Ior BJIo pLH3' | base64)" \
|
|
15
|
+
-H "Content-Type: application/json"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Expected Response
|
|
19
|
+
```json
|
|
20
|
+
[
|
|
21
|
+
{
|
|
22
|
+
"id": 1,
|
|
23
|
+
"title": {"rendered": "Hello world!"},
|
|
24
|
+
"status": "publish",
|
|
25
|
+
"link": "https://your-site.com/hello-world/"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Create a Test Post
|
|
31
|
+
|
|
32
|
+
### Using curl
|
|
33
|
+
```bash
|
|
34
|
+
curl -X POST "YOUR_SITE_URL/wp-json/wp/v2/posts" \
|
|
35
|
+
-H "Authorization: Basic $(echo -n 'admin:iqMC L5Eh JexQ 0Ior BJIo pLH3' | base64)" \
|
|
36
|
+
-H "Content-Type: application/json" \
|
|
37
|
+
-d '{
|
|
38
|
+
"title": "MyAIDev Test Post",
|
|
39
|
+
"content": "<h2>Test Post</h2><p>This is a test post created via REST API.</p>",
|
|
40
|
+
"status": "draft",
|
|
41
|
+
"excerpt": "Test post from MyAIDev Method"
|
|
42
|
+
}'
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Expected Response
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"id": 123,
|
|
49
|
+
"title": {"rendered": "MyAIDev Test Post"},
|
|
50
|
+
"status": "draft",
|
|
51
|
+
"link": "https://your-site.com/?p=123",
|
|
52
|
+
"content": {"rendered": "<h2>Test Post</h2><p>This is a test post created via REST API.</p>"}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Using JavaScript/Node.js
|
|
57
|
+
|
|
58
|
+
### Basic Example
|
|
59
|
+
```javascript
|
|
60
|
+
import fetch from 'node-fetch';
|
|
61
|
+
|
|
62
|
+
const siteUrl = 'YOUR_SITE_URL';
|
|
63
|
+
const username = 'admin';
|
|
64
|
+
const appPassword = 'iqMC L5Eh JexQ 0Ior BJIo pLH3';
|
|
65
|
+
const auth = Buffer.from(`${username}:${appPassword}`).toString('base64');
|
|
66
|
+
|
|
67
|
+
// Test connection
|
|
68
|
+
const response = await fetch(`${siteUrl}/wp-json/wp/v2/posts?per_page=1`, {
|
|
69
|
+
headers: {
|
|
70
|
+
'Authorization': `Basic ${auth}`,
|
|
71
|
+
'Content-Type': 'application/json'
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (response.ok) {
|
|
76
|
+
const posts = await response.json();
|
|
77
|
+
console.log('✅ Connection successful!', posts.length, 'posts found');
|
|
78
|
+
} else {
|
|
79
|
+
console.error('❌ Connection failed:', response.status, response.statusText);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Create Post Example
|
|
84
|
+
```javascript
|
|
85
|
+
// Create a new post
|
|
86
|
+
const postData = {
|
|
87
|
+
title: 'MyAIDev Method Test',
|
|
88
|
+
content: '<h2>Hello from MyAIDev!</h2><p>This post was created via REST API.</p>',
|
|
89
|
+
status: 'draft',
|
|
90
|
+
excerpt: 'Test post created by MyAIDev Method package.'
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const createResponse = await fetch(`${siteUrl}/wp-json/wp/v2/posts`, {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: {
|
|
96
|
+
'Authorization': `Basic ${auth}`,
|
|
97
|
+
'Content-Type': 'application/json'
|
|
98
|
+
},
|
|
99
|
+
body: JSON.stringify(postData)
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (createResponse.ok) {
|
|
103
|
+
const newPost = await createResponse.json();
|
|
104
|
+
console.log('✅ Post created!');
|
|
105
|
+
console.log('ID:', newPost.id);
|
|
106
|
+
console.log('Preview:', newPost.link + '?preview=true');
|
|
107
|
+
} else {
|
|
108
|
+
const error = await createResponse.text();
|
|
109
|
+
console.error('❌ Failed to create post:', error);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Testing Steps
|
|
114
|
+
|
|
115
|
+
1. **Replace YOUR_SITE_URL** with your actual WordPress site URL (e.g., `https://mysite.com`)
|
|
116
|
+
|
|
117
|
+
2. **Test connection first:**
|
|
118
|
+
```bash
|
|
119
|
+
node simple-wp-test.js
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
3. **Check WordPress admin** to see if the test post was created
|
|
123
|
+
|
|
124
|
+
4. **Verify the Application Password** is still active in WordPress Admin > Users > Profile
|
|
125
|
+
|
|
126
|
+
## Troubleshooting Common Issues
|
|
127
|
+
|
|
128
|
+
### 401 Unauthorized
|
|
129
|
+
- Check if Application Password is correct
|
|
130
|
+
- Verify username is 'admin'
|
|
131
|
+
- Ensure Application Password is still active
|
|
132
|
+
|
|
133
|
+
### 403 Forbidden
|
|
134
|
+
- User might not have permission to create posts
|
|
135
|
+
- Check if REST API is disabled
|
|
136
|
+
|
|
137
|
+
### 404 Not Found
|
|
138
|
+
- WordPress URL might be incorrect
|
|
139
|
+
- REST API might be disabled
|
|
140
|
+
- Permalinks might need to be refreshed
|
|
141
|
+
|
|
142
|
+
### SSL/Certificate Errors
|
|
143
|
+
- If using HTTPS, ensure SSL certificate is valid
|
|
144
|
+
- For testing, you might need to add SSL bypass (not recommended for production)
|
|
145
|
+
|
|
146
|
+
## WordPress Site Requirements
|
|
147
|
+
|
|
148
|
+
Your WordPress site needs:
|
|
149
|
+
- ✅ REST API enabled (default in WordPress 4.7+)
|
|
150
|
+
- ✅ Application Passwords enabled (WordPress 5.6+)
|
|
151
|
+
- ✅ User with post creation permissions
|
|
152
|
+
- ✅ Valid SSL certificate (if using HTTPS)
|
|
153
|
+
|
|
154
|
+
## Next Steps
|
|
155
|
+
|
|
156
|
+
1. Provide your WordPress site URL
|
|
157
|
+
2. Run the test script with your URL
|
|
158
|
+
3. Check if posts are created successfully
|
|
159
|
+
4. If working, the MyAIDev Method slash commands will work perfectly!
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* WordPress Integration Troubleshooting Script
|
|
5
|
+
* Quick diagnostic tool for MyAIDev Method WordPress integration
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fetch from 'node-fetch';
|
|
9
|
+
import fs from 'fs-extra';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
12
|
+
async function troubleshootWordPress() {
|
|
13
|
+
console.log('🔍 MyAIDev Method WordPress Troubleshooting');
|
|
14
|
+
console.log('═══════════════════════════════════════════════');
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
// 1. Check for .env file
|
|
18
|
+
console.log('\n1️⃣ Checking configuration...');
|
|
19
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
20
|
+
|
|
21
|
+
if (await fs.pathExists(envPath)) {
|
|
22
|
+
console.log('✅ .env file found');
|
|
23
|
+
|
|
24
|
+
const envContent = await fs.readFile(envPath, 'utf8');
|
|
25
|
+
const hasUrl = envContent.includes('WORDPRESS_URL');
|
|
26
|
+
const hasUsername = envContent.includes('WORDPRESS_USERNAME');
|
|
27
|
+
const hasPassword = envContent.includes('WORDPRESS_APP_PASSWORD');
|
|
28
|
+
|
|
29
|
+
console.log(` - WORDPRESS_URL: ${hasUrl ? '✅' : '❌'}`);
|
|
30
|
+
console.log(` - WORDPRESS_USERNAME: ${hasUsername ? '✅' : '❌'}`);
|
|
31
|
+
console.log(` - WORDPRESS_APP_PASSWORD: ${hasPassword ? '✅' : '❌'}`);
|
|
32
|
+
|
|
33
|
+
if (!hasUrl || !hasUsername || !hasPassword) {
|
|
34
|
+
console.log('⚠️ Missing WordPress credentials. Run: /myai-configure wordpress');
|
|
35
|
+
}
|
|
36
|
+
} else {
|
|
37
|
+
console.log('❌ .env file not found');
|
|
38
|
+
console.log('💡 Solution: Run /myai-configure wordpress to create configuration');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// 2. Check Claude configuration
|
|
42
|
+
console.log('\n2️⃣ Checking Claude Code setup...');
|
|
43
|
+
const claudeDir = path.join(process.cwd(), '.claude');
|
|
44
|
+
const commandsDir = path.join(claudeDir, 'commands');
|
|
45
|
+
|
|
46
|
+
if (await fs.pathExists(claudeDir)) {
|
|
47
|
+
console.log('✅ .claude directory found');
|
|
48
|
+
|
|
49
|
+
const wpPublishCommand = path.join(commandsDir, 'myai-wordpress-publish.md');
|
|
50
|
+
const wpConfigCommand = path.join(commandsDir, 'myai-configure.md');
|
|
51
|
+
|
|
52
|
+
console.log(` - WordPress publish command: ${await fs.pathExists(wpPublishCommand) ? '✅' : '❌'}`);
|
|
53
|
+
console.log(` - Configuration command: ${await fs.pathExists(wpConfigCommand) ? '✅' : '❌'}`);
|
|
54
|
+
} else {
|
|
55
|
+
console.log('❌ .claude directory not found');
|
|
56
|
+
console.log('💡 Solution: Run npx myaidev-method init --claude');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 3. Test connection if credentials exist
|
|
60
|
+
if (await fs.pathExists(envPath)) {
|
|
61
|
+
console.log('\n3️⃣ Testing WordPress connection...');
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
// Parse environment variables
|
|
65
|
+
const envContent = await fs.readFile(envPath, 'utf8');
|
|
66
|
+
const envVars = {};
|
|
67
|
+
|
|
68
|
+
envContent.split('\n').forEach(line => {
|
|
69
|
+
const [key, value] = line.split('=');
|
|
70
|
+
if (key && value) {
|
|
71
|
+
envVars[key.trim()] = value.trim();
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (envVars.WORDPRESS_URL && envVars.WORDPRESS_USERNAME && envVars.WORDPRESS_APP_PASSWORD) {
|
|
76
|
+
const auth = Buffer.from(`${envVars.WORDPRESS_USERNAME}:${envVars.WORDPRESS_APP_PASSWORD}`).toString('base64');
|
|
77
|
+
const apiUrl = `${envVars.WORDPRESS_URL}/wp-json/wp/v2`;
|
|
78
|
+
|
|
79
|
+
console.log(` Testing: ${apiUrl}`);
|
|
80
|
+
|
|
81
|
+
const response = await fetch(`${apiUrl}/posts?per_page=1`, {
|
|
82
|
+
headers: {
|
|
83
|
+
'Authorization': `Basic ${auth}`,
|
|
84
|
+
'Content-Type': 'application/json'
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (response.ok) {
|
|
89
|
+
console.log('✅ WordPress API connection successful');
|
|
90
|
+
|
|
91
|
+
// Test post creation
|
|
92
|
+
const testResponse = await fetch(`${apiUrl}/posts`, {
|
|
93
|
+
method: 'POST',
|
|
94
|
+
headers: {
|
|
95
|
+
'Authorization': `Basic ${auth}`,
|
|
96
|
+
'Content-Type': 'application/json'
|
|
97
|
+
},
|
|
98
|
+
body: JSON.stringify({
|
|
99
|
+
title: 'MyAIDev Test - ' + Date.now(),
|
|
100
|
+
content: 'Test post from troubleshooting script',
|
|
101
|
+
status: 'draft'
|
|
102
|
+
})
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (testResponse.ok) {
|
|
106
|
+
const post = await testResponse.json();
|
|
107
|
+
console.log('✅ Post creation successful');
|
|
108
|
+
console.log(` - Test post ID: ${post.id}`);
|
|
109
|
+
console.log(` - Preview: ${post.link}?preview=true`);
|
|
110
|
+
} else {
|
|
111
|
+
console.log('❌ Post creation failed');
|
|
112
|
+
const error = await testResponse.text();
|
|
113
|
+
console.log(` Error: ${error.substring(0, 100)}...`);
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
console.log(`❌ WordPress API connection failed: ${response.status}`);
|
|
117
|
+
console.log('💡 Check your WordPress URL and Application Password');
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
console.log('⚠️ Incomplete WordPress credentials in .env file');
|
|
121
|
+
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.log(`❌ Connection test failed: ${error.message}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
console.log('\n📋 Next Steps:');
|
|
128
|
+
console.log('1. If configuration is missing: /myai-configure wordpress');
|
|
129
|
+
console.log('2. If Claude setup is missing: npx myaidev-method init --claude');
|
|
130
|
+
console.log('3. If connection fails: Check WordPress Application Password');
|
|
131
|
+
console.log('4. For detailed help: Read WORDPRESS_INTEGRATION.md');
|
|
132
|
+
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error('❌ Troubleshooting failed:', error.message);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Export for programmatic use
|
|
139
|
+
export { troubleshootWordPress };
|
|
140
|
+
|
|
141
|
+
// Run if called directly
|
|
142
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
143
|
+
troubleshootWordPress();
|
|
144
|
+
}
|