myaidev-method 0.2.23 → 0.2.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/bin/cli.js +55 -2
  2. package/dist/server/.tsbuildinfo +1 -1
  3. package/package.json +6 -1
  4. package/src/config/workflows.js +28 -44
  5. package/src/lib/config-manager.js +470 -0
  6. package/src/lib/content-generator.js +427 -0
  7. package/src/lib/html-conversion-utils.js +843 -0
  8. package/src/lib/seo-optimizer.js +515 -0
  9. package/src/lib/wordpress-client.js +633 -0
  10. package/src/lib/workflow-installer.js +3 -3
  11. package/src/scripts/html-conversion-cli.js +526 -0
  12. package/src/scripts/init/configure.js +436 -0
  13. package/src/scripts/init/install.js +460 -0
  14. package/src/scripts/utils/file-utils.js +404 -0
  15. package/src/scripts/utils/logger.js +300 -0
  16. package/src/scripts/utils/write-content.js +293 -0
  17. package/src/templates/claude/agents/visual-content-generator.md +129 -4
  18. package/src/templates/claude/commands/myai-convert-html.md +186 -0
  19. package/src/templates/diagrams/architecture.d2 +52 -0
  20. package/src/templates/diagrams/flowchart.d2 +42 -0
  21. package/src/templates/diagrams/sequence.d2 +47 -0
  22. package/src/templates/docs/content-creation-guide.md +164 -0
  23. package/src/templates/docs/deployment-guide.md +336 -0
  24. package/src/templates/docs/visual-generation-guide.md +248 -0
  25. package/src/templates/docs/wordpress-publishing-guide.md +208 -0
  26. package/src/templates/infographics/comparison-table.html +347 -0
  27. package/src/templates/infographics/data-chart.html +268 -0
  28. package/src/templates/infographics/process-flow.html +365 -0
  29. /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;