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.
Files changed (30) hide show
  1. package/bin/cli.js +218 -38
  2. package/dist/server/.tsbuildinfo +1 -1
  3. package/package.json +10 -5
  4. package/src/config/workflows.js +28 -44
  5. package/src/lib/ascii-banner.js +214 -0
  6. package/src/lib/config-manager.js +470 -0
  7. package/src/lib/content-generator.js +427 -0
  8. package/src/lib/html-conversion-utils.js +843 -0
  9. package/src/lib/seo-optimizer.js +515 -0
  10. package/src/lib/wordpress-client.js +633 -0
  11. package/src/lib/workflow-installer.js +3 -3
  12. package/src/scripts/html-conversion-cli.js +526 -0
  13. package/src/scripts/init/configure.js +436 -0
  14. package/src/scripts/init/install.js +460 -0
  15. package/src/scripts/utils/file-utils.js +404 -0
  16. package/src/scripts/utils/logger.js +300 -0
  17. package/src/scripts/utils/write-content.js +293 -0
  18. package/src/templates/claude/agents/visual-content-generator.md +129 -4
  19. package/src/templates/claude/commands/myai-convert-html.md +186 -0
  20. package/src/templates/diagrams/architecture.d2 +52 -0
  21. package/src/templates/diagrams/flowchart.d2 +42 -0
  22. package/src/templates/diagrams/sequence.d2 +47 -0
  23. package/src/templates/docs/content-creation-guide.md +164 -0
  24. package/src/templates/docs/deployment-guide.md +336 -0
  25. package/src/templates/docs/visual-generation-guide.md +248 -0
  26. package/src/templates/docs/wordpress-publishing-guide.md +208 -0
  27. package/src/templates/infographics/comparison-table.html +347 -0
  28. package/src/templates/infographics/data-chart.html +268 -0
  29. package/src/templates/infographics/process-flow.html +365 -0
  30. /package/src/scripts/{wordpress-health-check.js → wordpress/wordpress-health-check.js} +0 -0
@@ -0,0 +1,515 @@
1
+ /**
2
+ * SEO Optimizer
3
+ * SEO optimization utilities for content generation
4
+ * Provides analysis, recommendations, and optimization functions
5
+ */
6
+
7
+ // SEO configuration defaults
8
+ const SEO_DEFAULTS = {
9
+ titleMinLength: 30,
10
+ titleMaxLength: 60,
11
+ descriptionMinLength: 120,
12
+ descriptionMaxLength: 160,
13
+ minKeywordDensity: 0.5,
14
+ maxKeywordDensity: 2.5,
15
+ minWordCount: 300,
16
+ idealWordCount: 1500,
17
+ maxWordCount: 5000,
18
+ minHeadings: 2,
19
+ minParagraphs: 3,
20
+ maxParagraphLength: 300,
21
+ idealSentenceLength: 20
22
+ };
23
+
24
+ /**
25
+ * SEO Optimizer Class
26
+ */
27
+ export class SEOOptimizer {
28
+ constructor(options = {}) {
29
+ this.config = { ...SEO_DEFAULTS, ...options };
30
+ }
31
+
32
+ /**
33
+ * Optimize content for SEO
34
+ * @param {string} content - Content to optimize
35
+ * @param {string[]} keywords - Target keywords
36
+ * @param {string} title - Content title
37
+ * @returns {Object} Optimization result with content and analysis
38
+ */
39
+ optimizeContent(content, keywords = [], title = '') {
40
+ let optimized = content;
41
+
42
+ // Ensure proper heading structure
43
+ optimized = this.optimizeHeadings(optimized, keywords);
44
+
45
+ // Optimize paragraph structure
46
+ optimized = this.optimizeParagraphs(optimized);
47
+
48
+ // Add keyword variations if density is too low
49
+ if (keywords.length > 0) {
50
+ const analysis = this.analyzeKeywordDensity(optimized, keywords);
51
+ if (analysis.overallDensity < this.config.minKeywordDensity) {
52
+ optimized = this.enhanceKeywordPresence(optimized, keywords);
53
+ }
54
+ }
55
+
56
+ // Analyze the optimized content
57
+ const seoAnalysis = this.analyzeSEO(optimized, keywords, title);
58
+
59
+ return {
60
+ content: optimized,
61
+ analysis: seoAnalysis,
62
+ optimized: true
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Generate meta description from content
68
+ * @param {string} content - Content to summarize
69
+ * @param {number} [maxLength=160] - Maximum description length
70
+ * @returns {string} Meta description
71
+ */
72
+ generateMetaDescription(content, maxLength = this.config.descriptionMaxLength) {
73
+ // Remove markdown formatting
74
+ let text = content
75
+ .replace(/^#{1,6}\s+.+$/gm, '') // Remove headings
76
+ .replace(/\*\*(.+?)\*\*/g, '$1') // Remove bold
77
+ .replace(/\*(.+?)\*/g, '$1') // Remove italic
78
+ .replace(/\[(.+?)\]\(.+?\)/g, '$1') // Remove links
79
+ .replace(/`(.+?)`/g, '$1') // Remove code
80
+ .replace(/\n+/g, ' ') // Replace newlines with spaces
81
+ .trim();
82
+
83
+ // Get the first paragraph or substantial text
84
+ const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0);
85
+ let description = '';
86
+
87
+ for (const sentence of sentences) {
88
+ const trimmed = sentence.trim();
89
+ if (description.length + trimmed.length + 2 <= maxLength) {
90
+ description += (description ? '. ' : '') + trimmed;
91
+ } else {
92
+ break;
93
+ }
94
+ }
95
+
96
+ // Ensure we have a complete thought
97
+ if (description.length < this.config.descriptionMinLength && sentences.length > 0) {
98
+ description = sentences[0].trim();
99
+ if (description.length > maxLength) {
100
+ description = description.substring(0, maxLength - 3) + '...';
101
+ }
102
+ }
103
+
104
+ // Add period if missing
105
+ if (description && !description.match(/[.!?]$/)) {
106
+ description += '.';
107
+ }
108
+
109
+ return description;
110
+ }
111
+
112
+ /**
113
+ * Generate URL-friendly slug from title
114
+ * @param {string} title - Title to slugify
115
+ * @returns {string} URL-safe slug
116
+ */
117
+ generateSlug(title) {
118
+ return title
119
+ .toLowerCase()
120
+ .trim()
121
+ // Remove special characters except spaces and hyphens
122
+ .replace(/[^\w\s-]/g, '')
123
+ // Replace spaces and underscores with hyphens
124
+ .replace(/[\s_]+/g, '-')
125
+ // Remove multiple consecutive hyphens
126
+ .replace(/-+/g, '-')
127
+ // Remove leading/trailing hyphens
128
+ .replace(/^-+|-+$/g, '');
129
+ }
130
+
131
+ /**
132
+ * Analyze SEO metrics for content
133
+ * @param {string} content - Content to analyze
134
+ * @param {string[]} keywords - Target keywords
135
+ * @param {string} [title=''] - Content title
136
+ * @returns {Object} SEO analysis result
137
+ */
138
+ analyzeSEO(content, keywords = [], title = '') {
139
+ const issues = [];
140
+ const recommendations = [];
141
+ let score = 100;
142
+
143
+ // Word count analysis
144
+ const wordCount = this.getWordCount(content);
145
+ if (wordCount < this.config.minWordCount) {
146
+ issues.push(`Content is too short (${wordCount} words). Minimum recommended: ${this.config.minWordCount}`);
147
+ score -= 15;
148
+ } else if (wordCount < this.config.idealWordCount) {
149
+ recommendations.push(`Consider expanding content to ${this.config.idealWordCount}+ words for better SEO`);
150
+ score -= 5;
151
+ } else if (wordCount > this.config.maxWordCount) {
152
+ recommendations.push('Content is quite long. Consider splitting into multiple articles');
153
+ }
154
+
155
+ // Title analysis
156
+ if (title) {
157
+ const titleAnalysis = this.analyzeTitle(title, keywords);
158
+ issues.push(...titleAnalysis.issues);
159
+ recommendations.push(...titleAnalysis.recommendations);
160
+ score -= titleAnalysis.issues.length * 10;
161
+ }
162
+
163
+ // Heading analysis
164
+ const headingAnalysis = this.analyzeHeadings(content, keywords);
165
+ issues.push(...headingAnalysis.issues);
166
+ recommendations.push(...headingAnalysis.recommendations);
167
+ score -= headingAnalysis.issues.length * 5;
168
+
169
+ // Keyword analysis
170
+ if (keywords.length > 0) {
171
+ const keywordAnalysis = this.analyzeKeywordDensity(content, keywords);
172
+ issues.push(...keywordAnalysis.issues);
173
+ recommendations.push(...keywordAnalysis.recommendations);
174
+ score -= keywordAnalysis.issues.length * 8;
175
+ }
176
+
177
+ // Readability analysis
178
+ const readabilityAnalysis = this.analyzeReadability(content);
179
+ issues.push(...readabilityAnalysis.issues);
180
+ recommendations.push(...readabilityAnalysis.recommendations);
181
+ score -= readabilityAnalysis.issues.length * 3;
182
+
183
+ // Structure analysis
184
+ const structureAnalysis = this.analyzeStructure(content);
185
+ issues.push(...structureAnalysis.issues);
186
+ recommendations.push(...structureAnalysis.recommendations);
187
+ score -= structureAnalysis.issues.length * 5;
188
+
189
+ // Ensure score is within bounds
190
+ score = Math.max(0, Math.min(100, score));
191
+
192
+ return {
193
+ score,
194
+ wordCount,
195
+ issues,
196
+ recommendations,
197
+ grade: this.getGrade(score),
198
+ keywordDensity: keywords.length > 0 ? this.analyzeKeywordDensity(content, keywords).overallDensity : null
199
+ };
200
+ }
201
+
202
+ /**
203
+ * Analyze title for SEO
204
+ * @param {string} title - Title to analyze
205
+ * @param {string[]} keywords - Target keywords
206
+ * @returns {Object} Title analysis
207
+ */
208
+ analyzeTitle(title, keywords = []) {
209
+ const issues = [];
210
+ const recommendations = [];
211
+
212
+ // Length check
213
+ if (title.length < this.config.titleMinLength) {
214
+ issues.push(`Title is too short (${title.length} chars). Minimum: ${this.config.titleMinLength}`);
215
+ } else if (title.length > this.config.titleMaxLength) {
216
+ issues.push(`Title is too long (${title.length} chars). Maximum: ${this.config.titleMaxLength}`);
217
+ }
218
+
219
+ // Keyword presence
220
+ if (keywords.length > 0) {
221
+ const titleLower = title.toLowerCase();
222
+ const primaryKeyword = keywords[0].toLowerCase();
223
+
224
+ if (!titleLower.includes(primaryKeyword)) {
225
+ recommendations.push(`Consider including primary keyword "${keywords[0]}" in the title`);
226
+ }
227
+
228
+ // Check if keyword is near the beginning
229
+ const keywordIndex = titleLower.indexOf(primaryKeyword);
230
+ if (keywordIndex > 20) {
231
+ recommendations.push('Move primary keyword closer to the beginning of the title');
232
+ }
233
+ }
234
+
235
+ // Power words check
236
+ const powerWords = ['ultimate', 'complete', 'essential', 'proven', 'best', 'top', 'guide', 'how to'];
237
+ const hassPowerWord = powerWords.some(word => title.toLowerCase().includes(word));
238
+ if (!hassPowerWord) {
239
+ recommendations.push('Consider adding a power word to make the title more compelling');
240
+ }
241
+
242
+ return { issues, recommendations };
243
+ }
244
+
245
+ /**
246
+ * Analyze headings structure
247
+ * @param {string} content - Content to analyze
248
+ * @param {string[]} keywords - Target keywords
249
+ * @returns {Object} Heading analysis
250
+ */
251
+ analyzeHeadings(content, keywords = []) {
252
+ const issues = [];
253
+ const recommendations = [];
254
+
255
+ const headings = content.match(/^#{1,6}\s+.+$/gm) || [];
256
+ const h2Count = (content.match(/^##\s+.+$/gm) || []).length;
257
+ const h3Count = (content.match(/^###\s+.+$/gm) || []).length;
258
+
259
+ // Check heading count
260
+ if (headings.length < this.config.minHeadings) {
261
+ issues.push(`Not enough headings (${headings.length}). Add at least ${this.config.minHeadings} headings`);
262
+ }
263
+
264
+ // Check H2 subheadings
265
+ if (h2Count < 2) {
266
+ recommendations.push('Add more H2 subheadings to improve content structure');
267
+ }
268
+
269
+ // Check for keyword in headings
270
+ if (keywords.length > 0) {
271
+ const keywordsInHeadings = keywords.filter(kw =>
272
+ headings.some(h => h.toLowerCase().includes(kw.toLowerCase()))
273
+ );
274
+
275
+ if (keywordsInHeadings.length === 0) {
276
+ recommendations.push('Include at least one keyword in your headings');
277
+ }
278
+ }
279
+
280
+ // Check heading hierarchy
281
+ const hasH1 = (content.match(/^#\s+.+$/gm) || []).length > 0;
282
+ if (hasH1 && h2Count === 0) {
283
+ recommendations.push('Add H2 subheadings under your main heading');
284
+ }
285
+
286
+ return { issues, recommendations };
287
+ }
288
+
289
+ /**
290
+ * Analyze keyword density
291
+ * @param {string} content - Content to analyze
292
+ * @param {string[]} keywords - Keywords to check
293
+ * @returns {Object} Keyword density analysis
294
+ */
295
+ analyzeKeywordDensity(content, keywords) {
296
+ const issues = [];
297
+ const recommendations = [];
298
+ const wordCount = this.getWordCount(content);
299
+ const contentLower = content.toLowerCase();
300
+
301
+ const densities = {};
302
+ let totalOccurrences = 0;
303
+
304
+ for (const keyword of keywords) {
305
+ const keywordLower = keyword.toLowerCase();
306
+ const regex = new RegExp(keywordLower.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
307
+ const matches = content.match(regex) || [];
308
+ const count = matches.length;
309
+ const density = (count / wordCount) * 100;
310
+
311
+ densities[keyword] = { count, density: density.toFixed(2) };
312
+ totalOccurrences += count;
313
+
314
+ if (density < this.config.minKeywordDensity) {
315
+ recommendations.push(`Keyword "${keyword}" density is low (${density.toFixed(2)}%). Consider adding more instances`);
316
+ } else if (density > this.config.maxKeywordDensity) {
317
+ issues.push(`Keyword "${keyword}" density is too high (${density.toFixed(2)}%). Risk of keyword stuffing`);
318
+ }
319
+ }
320
+
321
+ const overallDensity = (totalOccurrences / wordCount) * 100;
322
+
323
+ // Check first paragraph
324
+ const firstParagraph = content.split('\n\n')[0] || '';
325
+ const primaryKeyword = keywords[0]?.toLowerCase();
326
+ if (primaryKeyword && !firstParagraph.toLowerCase().includes(primaryKeyword)) {
327
+ recommendations.push('Include primary keyword in the first paragraph');
328
+ }
329
+
330
+ return {
331
+ densities,
332
+ overallDensity: parseFloat(overallDensity.toFixed(2)),
333
+ issues,
334
+ recommendations
335
+ };
336
+ }
337
+
338
+ /**
339
+ * Analyze content readability
340
+ * @param {string} content - Content to analyze
341
+ * @returns {Object} Readability analysis
342
+ */
343
+ analyzeReadability(content) {
344
+ const issues = [];
345
+ const recommendations = [];
346
+
347
+ const sentences = content.split(/[.!?]+/).filter(s => s.trim().length > 0);
348
+ const words = this.getWords(content);
349
+
350
+ // Average sentence length
351
+ const avgSentenceLength = words.length / sentences.length;
352
+ if (avgSentenceLength > 25) {
353
+ issues.push(`Average sentence length is too high (${Math.round(avgSentenceLength)} words). Aim for 15-20 words`);
354
+ } else if (avgSentenceLength < 10) {
355
+ recommendations.push('Sentences are quite short. Consider adding more complex sentences for variety');
356
+ }
357
+
358
+ // Paragraph length check
359
+ const paragraphs = content.split(/\n\n+/).filter(p => p.trim().length > 0);
360
+ const longParagraphs = paragraphs.filter(p => this.getWordCount(p) > this.config.maxParagraphLength);
361
+ if (longParagraphs.length > 0) {
362
+ recommendations.push(`${longParagraphs.length} paragraph(s) are too long. Consider breaking them up`);
363
+ }
364
+
365
+ // Passive voice check (simplified)
366
+ const passivePatterns = /\b(was|were|is|are|been|being)\s+\w+ed\b/gi;
367
+ const passiveMatches = content.match(passivePatterns) || [];
368
+ const passivePercentage = (passiveMatches.length / sentences.length) * 100;
369
+ if (passivePercentage > 20) {
370
+ recommendations.push(`High passive voice usage (${Math.round(passivePercentage)}%). Consider using more active voice`);
371
+ }
372
+
373
+ return { issues, recommendations };
374
+ }
375
+
376
+ /**
377
+ * Analyze content structure
378
+ * @param {string} content - Content to analyze
379
+ * @returns {Object} Structure analysis
380
+ */
381
+ analyzeStructure(content) {
382
+ const issues = [];
383
+ const recommendations = [];
384
+
385
+ // Check for lists
386
+ const hasBulletList = /^[-*]\s+.+$/m.test(content);
387
+ const hasNumberedList = /^\d+\.\s+.+$/m.test(content);
388
+ if (!hasBulletList && !hasNumberedList) {
389
+ recommendations.push('Consider adding bullet points or numbered lists for better readability');
390
+ }
391
+
392
+ // Check for links
393
+ const linkCount = (content.match(/\[.+?\]\(.+?\)/g) || []).length;
394
+ if (linkCount === 0) {
395
+ recommendations.push('Consider adding internal or external links');
396
+ }
397
+
398
+ // Check for images (markdown)
399
+ const imageCount = (content.match(/!\[.+?\]\(.+?\)/g) || []).length;
400
+ if (imageCount === 0) {
401
+ recommendations.push('Consider adding images to improve engagement');
402
+ }
403
+
404
+ // Check for call-to-action
405
+ const ctaPatterns = /\b(subscribe|sign up|download|learn more|get started|contact|buy now)\b/gi;
406
+ const hasCTA = ctaPatterns.test(content);
407
+ if (!hasCTA) {
408
+ recommendations.push('Consider adding a call-to-action');
409
+ }
410
+
411
+ return { issues, recommendations };
412
+ }
413
+
414
+ /**
415
+ * Optimize heading structure
416
+ * @param {string} content - Content to optimize
417
+ * @param {string[]} keywords - Target keywords
418
+ * @returns {string} Optimized content
419
+ */
420
+ optimizeHeadings(content, keywords) {
421
+ // This is a helper function - actual heading optimization
422
+ // would typically be done by the content-writer agent
423
+ return content;
424
+ }
425
+
426
+ /**
427
+ * Optimize paragraph structure
428
+ * @param {string} content - Content to optimize
429
+ * @returns {string} Optimized content
430
+ */
431
+ optimizeParagraphs(content) {
432
+ // Split overly long paragraphs
433
+ const paragraphs = content.split(/\n\n+/);
434
+ const optimized = paragraphs.map(p => {
435
+ const wordCount = this.getWordCount(p);
436
+ if (wordCount > this.config.maxParagraphLength) {
437
+ // Try to split at sentence boundaries
438
+ const sentences = p.split(/([.!?]\s+)/);
439
+ let current = '';
440
+ const parts = [];
441
+
442
+ for (let i = 0; i < sentences.length; i += 2) {
443
+ const sentence = sentences[i] + (sentences[i + 1] || '');
444
+ if (this.getWordCount(current + sentence) > this.config.maxParagraphLength / 2 && current) {
445
+ parts.push(current.trim());
446
+ current = sentence;
447
+ } else {
448
+ current += sentence;
449
+ }
450
+ }
451
+ if (current.trim()) {
452
+ parts.push(current.trim());
453
+ }
454
+ return parts.join('\n\n');
455
+ }
456
+ return p;
457
+ });
458
+
459
+ return optimized.join('\n\n');
460
+ }
461
+
462
+ /**
463
+ * Enhance keyword presence in content
464
+ * @param {string} content - Content to enhance
465
+ * @param {string[]} keywords - Keywords to include
466
+ * @returns {string} Enhanced content
467
+ */
468
+ enhanceKeywordPresence(content, keywords) {
469
+ // This is a placeholder - actual keyword enhancement
470
+ // should be done carefully by the content-writer agent
471
+ // to maintain natural flow
472
+ return content;
473
+ }
474
+
475
+ /**
476
+ * Get word count
477
+ * @param {string} text - Text to count
478
+ * @returns {number} Word count
479
+ */
480
+ getWordCount(text) {
481
+ return this.getWords(text).length;
482
+ }
483
+
484
+ /**
485
+ * Get words array
486
+ * @param {string} text - Text to split
487
+ * @returns {string[]} Array of words
488
+ */
489
+ getWords(text) {
490
+ return text
491
+ .replace(/[#*`\[\]()]/g, ' ')
492
+ .split(/\s+/)
493
+ .filter(w => w.length > 0);
494
+ }
495
+
496
+ /**
497
+ * Get letter grade from score
498
+ * @param {number} score - SEO score
499
+ * @returns {string} Letter grade
500
+ */
501
+ getGrade(score) {
502
+ if (score >= 90) return 'A';
503
+ if (score >= 80) return 'B';
504
+ if (score >= 70) return 'C';
505
+ if (score >= 60) return 'D';
506
+ return 'F';
507
+ }
508
+ }
509
+
510
+ // Factory function
511
+ export function createSEOOptimizer(options) {
512
+ return new SEOOptimizer(options);
513
+ }
514
+
515
+ export default SEOOptimizer;