myaidev-method 0.2.23 → 0.2.24-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +218 -38
- package/dist/server/.tsbuildinfo +1 -1
- package/package.json +10 -5
- package/src/config/workflows.js +28 -44
- package/src/lib/ascii-banner.js +214 -0
- package/src/lib/config-manager.js +470 -0
- package/src/lib/content-generator.js +427 -0
- package/src/lib/html-conversion-utils.js +843 -0
- package/src/lib/seo-optimizer.js +515 -0
- package/src/lib/wordpress-client.js +633 -0
- package/src/lib/workflow-installer.js +3 -3
- package/src/scripts/html-conversion-cli.js +526 -0
- package/src/scripts/init/configure.js +436 -0
- package/src/scripts/init/install.js +460 -0
- package/src/scripts/utils/file-utils.js +404 -0
- package/src/scripts/utils/logger.js +300 -0
- package/src/scripts/utils/write-content.js +293 -0
- package/src/templates/claude/agents/visual-content-generator.md +129 -4
- package/src/templates/claude/commands/myai-convert-html.md +186 -0
- package/src/templates/diagrams/architecture.d2 +52 -0
- package/src/templates/diagrams/flowchart.d2 +42 -0
- package/src/templates/diagrams/sequence.d2 +47 -0
- package/src/templates/docs/content-creation-guide.md +164 -0
- package/src/templates/docs/deployment-guide.md +336 -0
- package/src/templates/docs/visual-generation-guide.md +248 -0
- package/src/templates/docs/wordpress-publishing-guide.md +208 -0
- package/src/templates/infographics/comparison-table.html +347 -0
- package/src/templates/infographics/data-chart.html +268 -0
- package/src/templates/infographics/process-flow.html +365 -0
- /package/src/scripts/{wordpress-health-check.js → wordpress/wordpress-health-check.js} +0 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Generator
|
|
3
|
+
* Core content generation utilities for MyAIDev Method
|
|
4
|
+
* Works with content-writer agent to produce SEO-optimized content
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import fs from 'fs-extra';
|
|
9
|
+
import { getConfigManager } from './config-manager.js';
|
|
10
|
+
|
|
11
|
+
// Content type templates
|
|
12
|
+
const CONTENT_TEMPLATES = {
|
|
13
|
+
'blog-post': {
|
|
14
|
+
sections: ['introduction', 'main-content', 'conclusion', 'call-to-action'],
|
|
15
|
+
defaultWordCount: 1500,
|
|
16
|
+
frontmatter: ['title', 'description', 'date', 'author', 'categories', 'tags']
|
|
17
|
+
},
|
|
18
|
+
'tutorial': {
|
|
19
|
+
sections: ['overview', 'prerequisites', 'steps', 'troubleshooting', 'conclusion'],
|
|
20
|
+
defaultWordCount: 2000,
|
|
21
|
+
frontmatter: ['title', 'description', 'date', 'author', 'difficulty', 'time-to-complete']
|
|
22
|
+
},
|
|
23
|
+
'how-to': {
|
|
24
|
+
sections: ['introduction', 'requirements', 'step-by-step', 'tips', 'summary'],
|
|
25
|
+
defaultWordCount: 1200,
|
|
26
|
+
frontmatter: ['title', 'description', 'date', 'author', 'category']
|
|
27
|
+
},
|
|
28
|
+
'listicle': {
|
|
29
|
+
sections: ['introduction', 'list-items', 'summary'],
|
|
30
|
+
defaultWordCount: 1800,
|
|
31
|
+
frontmatter: ['title', 'description', 'date', 'author', 'categories']
|
|
32
|
+
},
|
|
33
|
+
'product-review': {
|
|
34
|
+
sections: ['overview', 'features', 'pros-cons', 'verdict', 'alternatives'],
|
|
35
|
+
defaultWordCount: 1500,
|
|
36
|
+
frontmatter: ['title', 'description', 'date', 'author', 'rating', 'product-name']
|
|
37
|
+
},
|
|
38
|
+
'comparison': {
|
|
39
|
+
sections: ['introduction', 'criteria', 'comparison-table', 'detailed-analysis', 'recommendation'],
|
|
40
|
+
defaultWordCount: 2500,
|
|
41
|
+
frontmatter: ['title', 'description', 'date', 'author', 'compared-items']
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Tone configurations
|
|
46
|
+
const TONE_SETTINGS = {
|
|
47
|
+
professional: {
|
|
48
|
+
vocabulary: 'formal',
|
|
49
|
+
contractions: false,
|
|
50
|
+
personalPronouns: 'limited',
|
|
51
|
+
sentenceLength: 'varied'
|
|
52
|
+
},
|
|
53
|
+
conversational: {
|
|
54
|
+
vocabulary: 'accessible',
|
|
55
|
+
contractions: true,
|
|
56
|
+
personalPronouns: 'frequent',
|
|
57
|
+
sentenceLength: 'shorter'
|
|
58
|
+
},
|
|
59
|
+
technical: {
|
|
60
|
+
vocabulary: 'specialized',
|
|
61
|
+
contractions: false,
|
|
62
|
+
personalPronouns: 'minimal',
|
|
63
|
+
sentenceLength: 'longer'
|
|
64
|
+
},
|
|
65
|
+
casual: {
|
|
66
|
+
vocabulary: 'informal',
|
|
67
|
+
contractions: true,
|
|
68
|
+
personalPronouns: 'frequent',
|
|
69
|
+
sentenceLength: 'shorter'
|
|
70
|
+
},
|
|
71
|
+
authoritative: {
|
|
72
|
+
vocabulary: 'formal',
|
|
73
|
+
contractions: false,
|
|
74
|
+
personalPronouns: 'minimal',
|
|
75
|
+
sentenceLength: 'varied'
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Content Generator Class
|
|
81
|
+
*/
|
|
82
|
+
export class ContentGenerator {
|
|
83
|
+
constructor(options = {}) {
|
|
84
|
+
this.configManager = options.configManager || getConfigManager();
|
|
85
|
+
this.outputDir = options.outputDir || './content';
|
|
86
|
+
this.defaultTone = options.tone || 'professional';
|
|
87
|
+
this.defaultWordCount = options.wordCount || 1500;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Initialize the content generator with config
|
|
92
|
+
* @returns {Promise<void>}
|
|
93
|
+
*/
|
|
94
|
+
async initialize() {
|
|
95
|
+
const config = await this.configManager.loadAll();
|
|
96
|
+
|
|
97
|
+
if (config.CONTENT_OUTPUT_DIR) {
|
|
98
|
+
this.outputDir = config.CONTENT_OUTPUT_DIR;
|
|
99
|
+
}
|
|
100
|
+
if (config.DEFAULT_TONE) {
|
|
101
|
+
this.defaultTone = config.DEFAULT_TONE;
|
|
102
|
+
}
|
|
103
|
+
if (config.DEFAULT_WORD_COUNT) {
|
|
104
|
+
this.defaultWordCount = parseInt(config.DEFAULT_WORD_COUNT, 10);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
await fs.ensureDir(this.outputDir);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Generate content specification for the content-writer agent
|
|
112
|
+
* @param {string} topic - Content topic
|
|
113
|
+
* @param {Object} options - Generation options
|
|
114
|
+
* @returns {Object} Content specification
|
|
115
|
+
*/
|
|
116
|
+
generateContentSpec(topic, options = {}) {
|
|
117
|
+
const {
|
|
118
|
+
contentType = 'blog-post',
|
|
119
|
+
wordCount = this.defaultWordCount,
|
|
120
|
+
tone = this.defaultTone,
|
|
121
|
+
keywords = [],
|
|
122
|
+
targetAudience = 'general',
|
|
123
|
+
additionalInstructions = ''
|
|
124
|
+
} = options;
|
|
125
|
+
|
|
126
|
+
const template = CONTENT_TEMPLATES[contentType] || CONTENT_TEMPLATES['blog-post'];
|
|
127
|
+
const toneSettings = TONE_SETTINGS[tone] || TONE_SETTINGS['professional'];
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
topic,
|
|
131
|
+
contentType,
|
|
132
|
+
template,
|
|
133
|
+
wordCount,
|
|
134
|
+
tone,
|
|
135
|
+
toneSettings,
|
|
136
|
+
keywords,
|
|
137
|
+
targetAudience,
|
|
138
|
+
additionalInstructions,
|
|
139
|
+
sections: template.sections,
|
|
140
|
+
requiredFrontmatter: template.frontmatter,
|
|
141
|
+
timestamp: new Date().toISOString()
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Select the appropriate template for content type
|
|
147
|
+
* @param {string} contentType - Type of content
|
|
148
|
+
* @returns {Object} Template configuration
|
|
149
|
+
*/
|
|
150
|
+
selectTemplate(contentType) {
|
|
151
|
+
return CONTENT_TEMPLATES[contentType] || CONTENT_TEMPLATES['blog-post'];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Apply custom rules from a rules file
|
|
156
|
+
* @param {string} content - Content to modify
|
|
157
|
+
* @param {string} rulesFile - Path to rules file
|
|
158
|
+
* @returns {Promise<string>} Modified content
|
|
159
|
+
*/
|
|
160
|
+
async applyCustomRules(content, rulesFile) {
|
|
161
|
+
try {
|
|
162
|
+
const exists = await fs.pathExists(rulesFile);
|
|
163
|
+
if (!exists) {
|
|
164
|
+
return content;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const rulesContent = await fs.readFile(rulesFile, 'utf-8');
|
|
168
|
+
const rules = this.parseRulesFile(rulesContent);
|
|
169
|
+
|
|
170
|
+
let modifiedContent = content;
|
|
171
|
+
|
|
172
|
+
// Apply text replacements
|
|
173
|
+
if (rules.replacements) {
|
|
174
|
+
for (const [pattern, replacement] of Object.entries(rules.replacements)) {
|
|
175
|
+
const regex = new RegExp(pattern, 'gi');
|
|
176
|
+
modifiedContent = modifiedContent.replace(regex, replacement);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Apply formatting rules
|
|
181
|
+
if (rules.formatting) {
|
|
182
|
+
modifiedContent = this.applyFormattingRules(modifiedContent, rules.formatting);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return modifiedContent;
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.warn(`Warning: Failed to apply custom rules: ${error.message}`);
|
|
188
|
+
return content;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Parse a rules file content
|
|
194
|
+
* @param {string} content - Rules file content
|
|
195
|
+
* @returns {Object} Parsed rules
|
|
196
|
+
*/
|
|
197
|
+
parseRulesFile(content) {
|
|
198
|
+
const rules = {
|
|
199
|
+
replacements: {},
|
|
200
|
+
formatting: {},
|
|
201
|
+
style: {}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const lines = content.split('\n');
|
|
205
|
+
let currentSection = null;
|
|
206
|
+
|
|
207
|
+
for (const line of lines) {
|
|
208
|
+
const trimmed = line.trim();
|
|
209
|
+
|
|
210
|
+
// Skip empty lines and comments
|
|
211
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Check for section headers
|
|
216
|
+
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
|
|
217
|
+
currentSection = trimmed.slice(1, -1).toLowerCase();
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Parse key-value pairs
|
|
222
|
+
const match = trimmed.match(/^([^=]+)=(.*)$/);
|
|
223
|
+
if (match && currentSection) {
|
|
224
|
+
const key = match[1].trim();
|
|
225
|
+
const value = match[2].trim();
|
|
226
|
+
|
|
227
|
+
if (rules[currentSection]) {
|
|
228
|
+
rules[currentSection][key] = value;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return rules;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Apply formatting rules to content
|
|
238
|
+
* @param {string} content - Content to format
|
|
239
|
+
* @param {Object} rules - Formatting rules
|
|
240
|
+
* @returns {string} Formatted content
|
|
241
|
+
*/
|
|
242
|
+
applyFormattingRules(content, rules) {
|
|
243
|
+
let result = content;
|
|
244
|
+
|
|
245
|
+
// Heading case transformation
|
|
246
|
+
if (rules.headingCase === 'title') {
|
|
247
|
+
result = result.replace(/^(#{1,6})\s+(.+)$/gm, (match, hashes, title) => {
|
|
248
|
+
return `${hashes} ${this.toTitleCase(title)}`;
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// List formatting
|
|
253
|
+
if (rules.listStyle === 'dash') {
|
|
254
|
+
result = result.replace(/^\*\s+/gm, '- ');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Convert string to title case
|
|
262
|
+
* @param {string} str - String to convert
|
|
263
|
+
* @returns {string} Title case string
|
|
264
|
+
*/
|
|
265
|
+
toTitleCase(str) {
|
|
266
|
+
const smallWords = ['a', 'an', 'the', 'and', 'but', 'or', 'for', 'nor', 'on', 'at', 'to', 'by', 'in', 'of'];
|
|
267
|
+
return str.split(' ').map((word, index) => {
|
|
268
|
+
const lower = word.toLowerCase();
|
|
269
|
+
if (index === 0 || !smallWords.includes(lower)) {
|
|
270
|
+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
271
|
+
}
|
|
272
|
+
return lower;
|
|
273
|
+
}).join(' ');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Generate YAML frontmatter for content
|
|
278
|
+
* @param {Object} data - Frontmatter data
|
|
279
|
+
* @returns {string} YAML frontmatter string
|
|
280
|
+
*/
|
|
281
|
+
generateFrontmatter(data) {
|
|
282
|
+
const lines = ['---'];
|
|
283
|
+
|
|
284
|
+
for (const [key, value] of Object.entries(data)) {
|
|
285
|
+
if (value === undefined || value === null) continue;
|
|
286
|
+
|
|
287
|
+
if (Array.isArray(value)) {
|
|
288
|
+
lines.push(`${key}:`);
|
|
289
|
+
for (const item of value) {
|
|
290
|
+
lines.push(` - "${item}"`);
|
|
291
|
+
}
|
|
292
|
+
} else if (typeof value === 'object') {
|
|
293
|
+
lines.push(`${key}:`);
|
|
294
|
+
for (const [subKey, subValue] of Object.entries(value)) {
|
|
295
|
+
lines.push(` ${subKey}: "${subValue}"`);
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
// Quote strings that might need it
|
|
299
|
+
const stringValue = String(value);
|
|
300
|
+
if (stringValue.includes(':') || stringValue.includes('#') || stringValue.includes('"')) {
|
|
301
|
+
lines.push(`${key}: "${stringValue.replace(/"/g, '\\"')}"`);
|
|
302
|
+
} else {
|
|
303
|
+
lines.push(`${key}: ${stringValue}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
lines.push('---');
|
|
309
|
+
lines.push('');
|
|
310
|
+
|
|
311
|
+
return lines.join('\n');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Create a content file with frontmatter
|
|
316
|
+
* @param {string} filename - Output filename
|
|
317
|
+
* @param {Object} frontmatter - Frontmatter data
|
|
318
|
+
* @param {string} content - Main content
|
|
319
|
+
* @returns {Promise<string>} Full file path
|
|
320
|
+
*/
|
|
321
|
+
async createContentFile(filename, frontmatter, content) {
|
|
322
|
+
const fullPath = path.join(this.outputDir, filename);
|
|
323
|
+
const frontmatterStr = this.generateFrontmatter(frontmatter);
|
|
324
|
+
const fullContent = frontmatterStr + content;
|
|
325
|
+
|
|
326
|
+
await fs.ensureDir(path.dirname(fullPath));
|
|
327
|
+
await fs.writeFile(fullPath, fullContent, 'utf-8');
|
|
328
|
+
|
|
329
|
+
return fullPath;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Generate a slug from a title
|
|
334
|
+
* @param {string} title - Title to slugify
|
|
335
|
+
* @returns {string} URL-safe slug
|
|
336
|
+
*/
|
|
337
|
+
generateSlug(title) {
|
|
338
|
+
return title
|
|
339
|
+
.toLowerCase()
|
|
340
|
+
.trim()
|
|
341
|
+
.replace(/[^\w\s-]/g, '')
|
|
342
|
+
.replace(/[\s_-]+/g, '-')
|
|
343
|
+
.replace(/^-+|-+$/g, '');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Generate a filename from title and date
|
|
348
|
+
* @param {string} title - Content title
|
|
349
|
+
* @param {string} [extension='md'] - File extension
|
|
350
|
+
* @returns {string} Generated filename
|
|
351
|
+
*/
|
|
352
|
+
generateFilename(title, extension = 'md') {
|
|
353
|
+
const date = new Date().toISOString().split('T')[0];
|
|
354
|
+
const slug = this.generateSlug(title);
|
|
355
|
+
return `${date}-${slug}.${extension}`;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Get available content types
|
|
360
|
+
* @returns {string[]} List of content types
|
|
361
|
+
*/
|
|
362
|
+
getContentTypes() {
|
|
363
|
+
return Object.keys(CONTENT_TEMPLATES);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Get available tones
|
|
368
|
+
* @returns {string[]} List of tones
|
|
369
|
+
*/
|
|
370
|
+
getTones() {
|
|
371
|
+
return Object.keys(TONE_SETTINGS);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Validate content options
|
|
376
|
+
* @param {Object} options - Options to validate
|
|
377
|
+
* @returns {{valid: boolean, errors: string[]}}
|
|
378
|
+
*/
|
|
379
|
+
validateOptions(options) {
|
|
380
|
+
const errors = [];
|
|
381
|
+
|
|
382
|
+
if (options.contentType && !CONTENT_TEMPLATES[options.contentType]) {
|
|
383
|
+
errors.push(`Invalid content type: ${options.contentType}. Valid types: ${Object.keys(CONTENT_TEMPLATES).join(', ')}`);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (options.tone && !TONE_SETTINGS[options.tone]) {
|
|
387
|
+
errors.push(`Invalid tone: ${options.tone}. Valid tones: ${Object.keys(TONE_SETTINGS).join(', ')}`);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (options.wordCount && (options.wordCount < 100 || options.wordCount > 10000)) {
|
|
391
|
+
errors.push('Word count must be between 100 and 10000');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
valid: errors.length === 0,
|
|
396
|
+
errors
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Get content statistics
|
|
402
|
+
* @param {string} content - Content to analyze
|
|
403
|
+
* @returns {Object} Content statistics
|
|
404
|
+
*/
|
|
405
|
+
getContentStats(content) {
|
|
406
|
+
const words = content.split(/\s+/).filter(w => w.length > 0);
|
|
407
|
+
const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 0);
|
|
408
|
+
const paragraphs = content.split(/\n\n+/).filter(p => p.trim().length > 0);
|
|
409
|
+
const headings = (content.match(/^#{1,6}\s+.+$/gm) || []).length;
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
wordCount: words.length,
|
|
413
|
+
sentenceCount: sentences.length,
|
|
414
|
+
paragraphCount: paragraphs.length,
|
|
415
|
+
headingCount: headings,
|
|
416
|
+
averageWordsPerSentence: sentences.length > 0 ? Math.round(words.length / sentences.length) : 0,
|
|
417
|
+
readingTime: Math.ceil(words.length / 200) // Assuming 200 words per minute
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Factory function for convenience
|
|
423
|
+
export function createContentGenerator(options) {
|
|
424
|
+
return new ContentGenerator(options);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
export default ContentGenerator;
|