myaidev-method 0.0.7 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/.claude/CLAUDE.md +52 -0
  2. package/.claude/agents/content-writer.md +155 -0
  3. package/.claude/commands/myai-configure.md +44 -0
  4. package/.claude/commands/myai-content-writer.md +78 -0
  5. package/.claude/commands/myai-wordpress-publish.md +120 -0
  6. package/.claude/mcp/gutenberg-converter.js +447 -0
  7. package/.claude/mcp/mcp-config.json +101 -0
  8. package/.claude/mcp/wordpress-server-simple.js +182 -0
  9. package/.claude/mcp/wordpress-server.js +1277 -0
  10. package/.claude/settings.local.json +12 -0
  11. package/COOLIFY_DEPLOYMENT.md +750 -0
  12. package/README.md +6 -6
  13. package/WORDPRESS_ADMIN_SCRIPTS.md +474 -0
  14. package/bin/cli.js +17 -22
  15. package/dist/mcp/gutenberg-converter.js +447 -0
  16. package/dist/mcp/mcp-config.json +101 -0
  17. package/dist/mcp/wordpress-server-simple.js +182 -0
  18. package/dist/mcp/wordpress-server.js +1277 -0
  19. package/package.json +29 -5
  20. package/src/lib/coolify-utils.js +380 -0
  21. package/src/lib/report-synthesizer.js +504 -0
  22. package/src/lib/wordpress-admin-utils.js +703 -0
  23. package/src/mcp/health-check.js +190 -0
  24. package/src/mcp/mcp-launcher.js +237 -0
  25. package/src/scripts/coolify-deploy-app.js +287 -0
  26. package/src/scripts/coolify-list-resources.js +199 -0
  27. package/src/scripts/coolify-status.js +97 -0
  28. package/src/scripts/test-coolify-deploy.js +47 -0
  29. package/src/scripts/wordpress-comprehensive-report.js +325 -0
  30. package/src/scripts/wordpress-health-check.js +175 -0
  31. package/src/scripts/wordpress-performance-check.js +461 -0
  32. package/src/scripts/wordpress-security-scan.js +221 -0
  33. package/src/templates/claude/agents/coolify-deploy.md +563 -0
  34. package/src/templates/claude/agents/wordpress-admin.md +228 -271
  35. package/src/templates/claude/commands/myai-configure.md +10 -74
  36. package/src/templates/claude/commands/myai-coolify-deploy.md +172 -0
  37. package/src/templates/claude/commands/myai-wordpress-publish.md +16 -8
@@ -0,0 +1,703 @@
1
+ /**
2
+ * WordPress Admin Utilities
3
+ * Reusable functions for WordPress administration tasks
4
+ * Supports scriptable operations with predictable JSON output
5
+ */
6
+
7
+ import fetch from 'node-fetch';
8
+ import { readFileSync } from 'fs';
9
+ import { parse } from 'dotenv';
10
+
11
+ export class WordPressAdminUtils {
12
+ constructor(config = {}) {
13
+ // Load config from .env if not provided
14
+ if (!config.url || !config.username || !config.appPassword) {
15
+ const envConfig = this.loadEnvConfig();
16
+ config = { ...envConfig, ...config };
17
+ }
18
+
19
+ this.url = config.url?.replace(/\/$/, '');
20
+ this.username = config.username;
21
+ this.appPassword = config.appPassword;
22
+ this.authHeader = this.createAuthHeader(this.username, this.appPassword);
23
+ }
24
+
25
+ loadEnvConfig() {
26
+ try {
27
+ const envPath = process.env.ENV_PATH || '.env';
28
+ const envContent = readFileSync(envPath, 'utf8');
29
+ const parsed = parse(envContent);
30
+
31
+ return {
32
+ url: parsed.WORDPRESS_URL,
33
+ username: parsed.WORDPRESS_USERNAME,
34
+ appPassword: parsed.WORDPRESS_APP_PASSWORD
35
+ };
36
+ } catch (error) {
37
+ throw new Error(`Failed to load WordPress configuration: ${error.message}`);
38
+ }
39
+ }
40
+
41
+ createAuthHeader(username, appPassword) {
42
+ const credentials = `${username}:${appPassword}`;
43
+ return `Basic ${Buffer.from(credentials).toString('base64')}`;
44
+ }
45
+
46
+ /**
47
+ * Make authenticated WordPress REST API request
48
+ */
49
+ async request(endpoint, options = {}) {
50
+ const url = `${this.url}/wp-json${endpoint}`;
51
+ const headers = {
52
+ 'Authorization': this.authHeader,
53
+ 'Content-Type': 'application/json',
54
+ ...options.headers
55
+ };
56
+
57
+ try {
58
+ const response = await fetch(url, { ...options, headers });
59
+
60
+ if (!response.ok) {
61
+ const errorText = await response.text();
62
+ throw new Error(`HTTP ${response.status}: ${errorText}`);
63
+ }
64
+
65
+ return await response.json();
66
+ } catch (error) {
67
+ throw new Error(`WordPress API request failed: ${error.message}`);
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Site Information
73
+ */
74
+ async getSiteInfo() {
75
+ const [siteData, users, plugins] = await Promise.all([
76
+ this.request('/'),
77
+ this.request('/wp/v2/users?per_page=1').catch(() => []),
78
+ this.request('/wp/v2/plugins').catch(() => [])
79
+ ]);
80
+
81
+ return {
82
+ name: siteData.name,
83
+ description: siteData.description,
84
+ url: siteData.url,
85
+ wordpress_version: siteData.version || 'unknown',
86
+ api_version: siteData.namespaces?.includes('wp/v2') ? 'v2' : 'unknown',
87
+ timezone: siteData.timezone_string || 'UTC',
88
+ user_count: await this.getUserCount(),
89
+ plugin_count: plugins.length || 0
90
+ };
91
+ }
92
+
93
+ /**
94
+ * User Management
95
+ */
96
+ async getUserCount() {
97
+ try {
98
+ const response = await fetch(`${this.url}/wp-json/wp/v2/users?per_page=1`, {
99
+ headers: { 'Authorization': this.authHeader }
100
+ });
101
+ const total = response.headers.get('x-wp-total');
102
+ return parseInt(total) || 0;
103
+ } catch (error) {
104
+ return 0;
105
+ }
106
+ }
107
+
108
+ async getUsers(params = {}) {
109
+ const queryParams = new URLSearchParams({
110
+ per_page: 100,
111
+ context: 'edit',
112
+ ...params
113
+ });
114
+
115
+ return await this.request(`/wp/v2/users?${queryParams}`);
116
+ }
117
+
118
+ async getUserSecurityAudit() {
119
+ const users = await this.getUsers();
120
+
121
+ return users.map(user => ({
122
+ id: user.id,
123
+ username: user.username,
124
+ email: user.email,
125
+ roles: user.roles || [],
126
+ registered_date: user.registered_date,
127
+ capabilities: Object.keys(user.capabilities || {}),
128
+ security_flags: this.analyzeUserSecurity(user)
129
+ }));
130
+ }
131
+
132
+ analyzeUserSecurity(user) {
133
+ const flags = [];
134
+
135
+ // Check for admin users with weak patterns
136
+ if (user.roles?.includes('administrator')) {
137
+ if (['admin', 'administrator', 'root', 'test'].includes(user.username.toLowerCase())) {
138
+ flags.push('common_admin_username');
139
+ }
140
+ }
141
+
142
+ // Check for users without email
143
+ if (!user.email || user.email === '') {
144
+ flags.push('missing_email');
145
+ }
146
+
147
+ // Check for old accounts without recent activity
148
+ if (user.registered_date) {
149
+ const regDate = new Date(user.registered_date);
150
+ const yearOld = new Date();
151
+ yearOld.setFullYear(yearOld.getFullYear() - 1);
152
+
153
+ if (regDate < yearOld && !user.meta?.last_login) {
154
+ flags.push('inactive_old_account');
155
+ }
156
+ }
157
+
158
+ return flags;
159
+ }
160
+
161
+ /**
162
+ * Plugin Management
163
+ */
164
+ async getPlugins(status = null) {
165
+ const endpoint = status
166
+ ? `/wp/v2/plugins?status=${status}`
167
+ : '/wp/v2/plugins';
168
+
169
+ return await this.request(endpoint);
170
+ }
171
+
172
+ async getPluginSecurityStatus() {
173
+ const plugins = await this.getPlugins();
174
+
175
+ return plugins.map(plugin => ({
176
+ name: plugin.name,
177
+ slug: plugin.plugin,
178
+ version: plugin.version,
179
+ status: plugin.status,
180
+ author: plugin.author,
181
+ requires_wp: plugin.requires_wp,
182
+ requires_php: plugin.requires_php,
183
+ update_available: plugin.update?.new_version ? true : false,
184
+ new_version: plugin.update?.new_version || null
185
+ }));
186
+ }
187
+
188
+ /**
189
+ * Content Management
190
+ */
191
+ async getPosts(params = {}) {
192
+ const queryParams = new URLSearchParams({
193
+ per_page: 100,
194
+ ...params
195
+ });
196
+
197
+ return await this.request(`/wp/v2/posts?${queryParams}`);
198
+ }
199
+
200
+ async getPostStats() {
201
+ const [published, draft, pending, trash] = await Promise.all([
202
+ this.getPosts({ status: 'publish', per_page: 1 }).then(r => this.getTotalFromResponse(r)),
203
+ this.getPosts({ status: 'draft', per_page: 1 }).then(r => this.getTotalFromResponse(r)),
204
+ this.getPosts({ status: 'pending', per_page: 1 }).then(r => this.getTotalFromResponse(r)),
205
+ this.getPosts({ status: 'trash', per_page: 1 }).then(r => this.getTotalFromResponse(r))
206
+ ]);
207
+
208
+ return { published, draft, pending, trash, total: published + draft + pending + trash };
209
+ }
210
+
211
+ async getMediaStats() {
212
+ try {
213
+ const media = await this.request('/wp/v2/media?per_page=100');
214
+
215
+ const stats = {
216
+ total: media.length,
217
+ by_type: {},
218
+ total_size: 0,
219
+ unattached: 0
220
+ };
221
+
222
+ media.forEach(item => {
223
+ const type = item.mime_type?.split('/')[0] || 'unknown';
224
+ stats.by_type[type] = (stats.by_type[type] || 0) + 1;
225
+
226
+ if (item.source_url) {
227
+ // Approximate size - would need actual file size from filesystem
228
+ stats.total_size += 0; // Placeholder
229
+ }
230
+
231
+ if (!item.post) {
232
+ stats.unattached++;
233
+ }
234
+ });
235
+
236
+ return stats;
237
+ } catch (error) {
238
+ return { total: 0, by_type: {}, total_size: 0, unattached: 0, error: error.message };
239
+ }
240
+ }
241
+
242
+ getTotalFromResponse(response) {
243
+ // WordPress includes total in headers, but we need to handle array response
244
+ return Array.isArray(response) ? response.length : 0;
245
+ }
246
+
247
+ /**
248
+ * Theme Management
249
+ */
250
+ async getThemes() {
251
+ return await this.request('/wp/v2/themes');
252
+ }
253
+
254
+ async getActiveTheme() {
255
+ const themes = await this.getThemes();
256
+ return themes.find(theme => theme.status === 'active');
257
+ }
258
+
259
+ /**
260
+ * Performance Metrics
261
+ */
262
+ async getPerformanceMetrics() {
263
+ const start = Date.now();
264
+ await this.request('/');
265
+ const apiResponseTime = Date.now() - start;
266
+
267
+ const [postStats, mediaStats, plugins, themes] = await Promise.all([
268
+ this.getPostStats(),
269
+ this.getMediaStats(),
270
+ this.getPlugins().catch(() => []),
271
+ this.getThemes().catch(() => [])
272
+ ]);
273
+
274
+ return {
275
+ api_response_time_ms: apiResponseTime,
276
+ post_count: postStats.total,
277
+ media_count: mediaStats.total,
278
+ plugin_count: plugins.length,
279
+ theme_count: themes.length,
280
+ active_plugins: plugins.filter(p => p.status === 'active').length
281
+ };
282
+ }
283
+
284
+ /**
285
+ * Health Check Components
286
+ */
287
+ async checkWordPressVersion() {
288
+ try {
289
+ const siteInfo = await this.getSiteInfo();
290
+ const version = siteInfo.wordpress_version;
291
+
292
+ // Would need to check against latest version from wordpress.org
293
+ // For now, return version info
294
+ return {
295
+ check: 'wordpress_version',
296
+ status: 'info',
297
+ message: `WordPress ${version}`,
298
+ current_version: version,
299
+ details: 'Version check requires external API to determine if update available'
300
+ };
301
+ } catch (error) {
302
+ return {
303
+ check: 'wordpress_version',
304
+ status: 'error',
305
+ message: error.message
306
+ };
307
+ }
308
+ }
309
+
310
+ async checkPluginHealth() {
311
+ try {
312
+ const plugins = await this.getPluginSecurityStatus();
313
+ const outdated = plugins.filter(p => p.update_available);
314
+ const inactive = plugins.filter(p => p.status === 'inactive');
315
+
316
+ return {
317
+ check: 'plugin_health',
318
+ status: outdated.length > 5 ? 'warning' : 'passed',
319
+ message: `${plugins.length} plugins installed, ${outdated.length} updates available`,
320
+ total_plugins: plugins.length,
321
+ updates_available: outdated.length,
322
+ inactive_plugins: inactive.length,
323
+ outdated_plugins: outdated.map(p => ({ name: p.name, current: p.version, new: p.new_version }))
324
+ };
325
+ } catch (error) {
326
+ return {
327
+ check: 'plugin_health',
328
+ status: 'error',
329
+ message: error.message
330
+ };
331
+ }
332
+ }
333
+
334
+ async checkContentHealth() {
335
+ try {
336
+ const postStats = await this.getPostStats();
337
+ const mediaStats = await this.getMediaStats();
338
+
339
+ const warnings = [];
340
+ if (postStats.trash > 50) warnings.push(`${postStats.trash} posts in trash`);
341
+ if (postStats.draft > 100) warnings.push(`${postStats.draft} draft posts`);
342
+ if (mediaStats.unattached > 20) warnings.push(`${mediaStats.unattached} unattached media files`);
343
+
344
+ return {
345
+ check: 'content_health',
346
+ status: warnings.length > 0 ? 'warning' : 'passed',
347
+ message: warnings.length > 0 ? warnings.join(', ') : 'Content is healthy',
348
+ post_stats: postStats,
349
+ media_stats: mediaStats,
350
+ recommendations: warnings.length > 0 ? ['Consider cleanup of trash and drafts'] : []
351
+ };
352
+ } catch (error) {
353
+ return {
354
+ check: 'content_health',
355
+ status: 'error',
356
+ message: error.message
357
+ };
358
+ }
359
+ }
360
+
361
+ async checkUserSecurity() {
362
+ try {
363
+ const audit = await this.getUserSecurityAudit();
364
+ const admins = audit.filter(u => u.roles.includes('administrator'));
365
+ const flaggedUsers = audit.filter(u => u.security_flags.length > 0);
366
+
367
+ return {
368
+ check: 'user_security',
369
+ status: flaggedUsers.length > 0 ? 'warning' : 'passed',
370
+ message: `${audit.length} users, ${admins.length} administrators, ${flaggedUsers.length} security concerns`,
371
+ total_users: audit.length,
372
+ admin_count: admins.length,
373
+ flagged_users: flaggedUsers.map(u => ({
374
+ username: u.username,
375
+ flags: u.security_flags
376
+ })),
377
+ recommendations: flaggedUsers.length > 0
378
+ ? ['Review flagged user accounts', 'Consider enforcing strong passwords']
379
+ : []
380
+ };
381
+ } catch (error) {
382
+ return {
383
+ check: 'user_security',
384
+ status: 'error',
385
+ message: error.message
386
+ };
387
+ }
388
+ }
389
+
390
+ async checkPerformance() {
391
+ try {
392
+ const metrics = await this.getPerformanceMetrics();
393
+
394
+ const status = metrics.api_response_time_ms > 1000 ? 'warning' : 'passed';
395
+ const message = `API response: ${metrics.api_response_time_ms}ms, ${metrics.active_plugins} active plugins`;
396
+
397
+ return {
398
+ check: 'performance',
399
+ status,
400
+ message,
401
+ metrics,
402
+ recommendations: status === 'warning'
403
+ ? ['Consider caching optimization', 'Review active plugin count']
404
+ : []
405
+ };
406
+ } catch (error) {
407
+ return {
408
+ check: 'performance',
409
+ status: 'error',
410
+ message: error.message
411
+ };
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Comprehensive Health Check
417
+ */
418
+ async runHealthCheck() {
419
+ const timestamp = new Date().toISOString();
420
+
421
+ try {
422
+ const checks = await Promise.all([
423
+ this.checkWordPressVersion(),
424
+ this.checkPluginHealth(),
425
+ this.checkContentHealth(),
426
+ this.checkUserSecurity(),
427
+ this.checkPerformance()
428
+ ]);
429
+
430
+ const critical = checks.filter(c => c.status === 'critical');
431
+ const warnings = checks.filter(c => c.status === 'warning');
432
+ const passed = checks.filter(c => c.status === 'passed');
433
+ const errors = checks.filter(c => c.status === 'error');
434
+
435
+ const score = this.calculateHealthScore(checks);
436
+
437
+ return {
438
+ success: true,
439
+ timestamp,
440
+ site: await this.getSiteInfo().catch(() => ({ url: this.url })),
441
+ overall_health: {
442
+ score,
443
+ grade: this.getHealthGrade(score),
444
+ status: critical.length > 0 ? 'critical' : (warnings.length > 0 ? 'warning' : 'healthy')
445
+ },
446
+ checks,
447
+ summary: {
448
+ total_checks: checks.length,
449
+ passed: passed.length,
450
+ warnings: warnings.length,
451
+ critical: critical.length,
452
+ errors: errors.length
453
+ },
454
+ recommendations: this.aggregateRecommendations(checks)
455
+ };
456
+ } catch (error) {
457
+ return {
458
+ success: false,
459
+ timestamp,
460
+ error: error.message,
461
+ site: { url: this.url }
462
+ };
463
+ }
464
+ }
465
+
466
+ calculateHealthScore(checks) {
467
+ const weights = {
468
+ passed: 1.0,
469
+ info: 0.9,
470
+ warning: 0.5,
471
+ critical: 0.0,
472
+ error: 0.3
473
+ };
474
+
475
+ let totalWeight = 0;
476
+ let totalScore = 0;
477
+
478
+ checks.forEach(check => {
479
+ const weight = weights[check.status] || 0.5;
480
+ totalWeight += 1;
481
+ totalScore += weight;
482
+ });
483
+
484
+ return totalWeight > 0 ? Math.round((totalScore / totalWeight) * 100) : 0;
485
+ }
486
+
487
+ getHealthGrade(score) {
488
+ if (score >= 90) return 'A';
489
+ if (score >= 80) return 'B';
490
+ if (score >= 70) return 'C';
491
+ if (score >= 60) return 'D';
492
+ return 'F';
493
+ }
494
+
495
+ aggregateRecommendations(checks) {
496
+ const recommendations = [];
497
+
498
+ checks.forEach(check => {
499
+ if (check.recommendations) {
500
+ recommendations.push(...check.recommendations);
501
+ }
502
+ });
503
+
504
+ // Deduplicate
505
+ return [...new Set(recommendations)];
506
+ }
507
+
508
+ /**
509
+ * Security Scan
510
+ */
511
+ async runSecurityScan() {
512
+ const timestamp = new Date().toISOString();
513
+
514
+ try {
515
+ const [userAudit, pluginStatus, siteInfo] = await Promise.all([
516
+ this.getUserSecurityAudit(),
517
+ this.getPluginSecurityStatus(),
518
+ this.getSiteInfo()
519
+ ]);
520
+
521
+ const vulnerabilities = [];
522
+ const warnings = [];
523
+
524
+ // Check plugins for updates (potential vulnerabilities)
525
+ const outdatedPlugins = pluginStatus.filter(p => p.update_available);
526
+ if (outdatedPlugins.length > 0) {
527
+ warnings.push({
528
+ type: 'outdated_plugins',
529
+ severity: 'medium',
530
+ count: outdatedPlugins.length,
531
+ details: outdatedPlugins.map(p => `${p.name} (${p.version} → ${p.new_version})`)
532
+ });
533
+ }
534
+
535
+ // Check user security
536
+ const flaggedUsers = userAudit.filter(u => u.security_flags.length > 0);
537
+ if (flaggedUsers.length > 0) {
538
+ warnings.push({
539
+ type: 'user_security',
540
+ severity: 'medium',
541
+ count: flaggedUsers.length,
542
+ details: flaggedUsers.map(u => `${u.username}: ${u.security_flags.join(', ')}`)
543
+ });
544
+ }
545
+
546
+ const securityScore = this.calculateSecurityScore(vulnerabilities, warnings);
547
+
548
+ return {
549
+ success: true,
550
+ timestamp,
551
+ site: siteInfo,
552
+ security_score: securityScore,
553
+ vulnerabilities,
554
+ warnings,
555
+ summary: {
556
+ critical_issues: vulnerabilities.length,
557
+ warnings: warnings.length,
558
+ status: vulnerabilities.length > 0 ? 'critical' : (warnings.length > 0 ? 'warning' : 'secure')
559
+ },
560
+ recommendations: this.getSecurityRecommendations(vulnerabilities, warnings)
561
+ };
562
+ } catch (error) {
563
+ return {
564
+ success: false,
565
+ timestamp,
566
+ error: error.message
567
+ };
568
+ }
569
+ }
570
+
571
+ calculateSecurityScore(vulnerabilities, warnings) {
572
+ let score = 100;
573
+ score -= vulnerabilities.length * 20; // Critical issues
574
+ score -= warnings.length * 5; // Warnings
575
+ return Math.max(0, score);
576
+ }
577
+
578
+ getSecurityRecommendations(vulnerabilities, warnings) {
579
+ const recommendations = [];
580
+
581
+ if (vulnerabilities.length > 0) {
582
+ recommendations.push('Address critical security vulnerabilities immediately');
583
+ }
584
+
585
+ warnings.forEach(warning => {
586
+ switch (warning.type) {
587
+ case 'outdated_plugins':
588
+ recommendations.push('Update all plugins to latest versions');
589
+ break;
590
+ case 'user_security':
591
+ recommendations.push('Review user accounts and strengthen security policies');
592
+ break;
593
+ }
594
+ });
595
+
596
+ return recommendations;
597
+ }
598
+
599
+ /**
600
+ * Format output for CLI
601
+ */
602
+ formatHealthReport(healthData) {
603
+ if (!healthData.success) {
604
+ return `ERROR: ${healthData.error}`;
605
+ }
606
+
607
+ const lines = [];
608
+ lines.push('='.repeat(60));
609
+ lines.push('WordPress Health Check Report');
610
+ lines.push('='.repeat(60));
611
+ lines.push(`Site: ${healthData.site.name || healthData.site.url}`);
612
+ lines.push(`Timestamp: ${healthData.timestamp}`);
613
+ lines.push('');
614
+ lines.push(`Overall Health: ${healthData.overall_health.grade} (${healthData.overall_health.score}/100)`);
615
+ lines.push(`Status: ${healthData.overall_health.status.toUpperCase()}`);
616
+ lines.push('');
617
+ lines.push('Check Results:');
618
+ lines.push('-'.repeat(60));
619
+
620
+ healthData.checks.forEach(check => {
621
+ const statusIcon = {
622
+ passed: '✓',
623
+ warning: '⚠',
624
+ critical: '✗',
625
+ error: '✗',
626
+ info: 'ℹ'
627
+ }[check.status] || '?';
628
+
629
+ lines.push(`${statusIcon} ${check.check}: ${check.message}`);
630
+ });
631
+
632
+ if (healthData.recommendations.length > 0) {
633
+ lines.push('');
634
+ lines.push('Recommendations:');
635
+ lines.push('-'.repeat(60));
636
+ healthData.recommendations.forEach((rec, i) => {
637
+ lines.push(`${i + 1}. ${rec}`);
638
+ });
639
+ }
640
+
641
+ lines.push('');
642
+ lines.push('='.repeat(60));
643
+
644
+ return lines.join('\n');
645
+ }
646
+
647
+ formatSecurityReport(securityData) {
648
+ if (!securityData.success) {
649
+ return `ERROR: ${securityData.error}`;
650
+ }
651
+
652
+ const lines = [];
653
+ lines.push('='.repeat(60));
654
+ lines.push('WordPress Security Scan Report');
655
+ lines.push('='.repeat(60));
656
+ lines.push(`Site: ${securityData.site.name || securityData.site.url}`);
657
+ lines.push(`Timestamp: ${securityData.timestamp}`);
658
+ lines.push('');
659
+ lines.push(`Security Score: ${securityData.security_score}/100`);
660
+ lines.push(`Status: ${securityData.summary.status.toUpperCase()}`);
661
+ lines.push('');
662
+
663
+ if (securityData.vulnerabilities.length > 0) {
664
+ lines.push('CRITICAL VULNERABILITIES:');
665
+ lines.push('-'.repeat(60));
666
+ securityData.vulnerabilities.forEach(vuln => {
667
+ lines.push(`✗ ${vuln.type} (${vuln.severity})`);
668
+ vuln.details.forEach(detail => lines.push(` - ${detail}`));
669
+ });
670
+ lines.push('');
671
+ }
672
+
673
+ if (securityData.warnings.length > 0) {
674
+ lines.push('WARNINGS:');
675
+ lines.push('-'.repeat(60));
676
+ securityData.warnings.forEach(warning => {
677
+ lines.push(`⚠ ${warning.type} (${warning.severity}) - ${warning.count} issues`);
678
+ if (warning.details.length <= 5) {
679
+ warning.details.forEach(detail => lines.push(` - ${detail}`));
680
+ } else {
681
+ warning.details.slice(0, 3).forEach(detail => lines.push(` - ${detail}`));
682
+ lines.push(` ... and ${warning.details.length - 3} more`);
683
+ }
684
+ });
685
+ lines.push('');
686
+ }
687
+
688
+ if (securityData.recommendations.length > 0) {
689
+ lines.push('RECOMMENDATIONS:');
690
+ lines.push('-'.repeat(60));
691
+ securityData.recommendations.forEach((rec, i) => {
692
+ lines.push(`${i + 1}. ${rec}`);
693
+ });
694
+ }
695
+
696
+ lines.push('');
697
+ lines.push('='.repeat(60));
698
+
699
+ return lines.join('\n');
700
+ }
701
+ }
702
+
703
+ export default WordPressAdminUtils;