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.
- package/.claude/CLAUDE.md +52 -0
- package/.claude/agents/content-writer.md +155 -0
- package/.claude/commands/myai-configure.md +44 -0
- package/.claude/commands/myai-content-writer.md +78 -0
- package/.claude/commands/myai-wordpress-publish.md +120 -0
- package/.claude/mcp/gutenberg-converter.js +447 -0
- package/.claude/mcp/mcp-config.json +101 -0
- package/.claude/mcp/wordpress-server-simple.js +182 -0
- package/.claude/mcp/wordpress-server.js +1277 -0
- package/.claude/settings.local.json +12 -0
- package/COOLIFY_DEPLOYMENT.md +750 -0
- package/README.md +6 -6
- package/WORDPRESS_ADMIN_SCRIPTS.md +474 -0
- package/bin/cli.js +17 -22
- package/dist/mcp/gutenberg-converter.js +447 -0
- package/dist/mcp/mcp-config.json +101 -0
- package/dist/mcp/wordpress-server-simple.js +182 -0
- package/dist/mcp/wordpress-server.js +1277 -0
- package/package.json +29 -5
- package/src/lib/coolify-utils.js +380 -0
- package/src/lib/report-synthesizer.js +504 -0
- package/src/lib/wordpress-admin-utils.js +703 -0
- package/src/mcp/health-check.js +190 -0
- package/src/mcp/mcp-launcher.js +237 -0
- package/src/scripts/coolify-deploy-app.js +287 -0
- package/src/scripts/coolify-list-resources.js +199 -0
- package/src/scripts/coolify-status.js +97 -0
- package/src/scripts/test-coolify-deploy.js +47 -0
- package/src/scripts/wordpress-comprehensive-report.js +325 -0
- package/src/scripts/wordpress-health-check.js +175 -0
- package/src/scripts/wordpress-performance-check.js +461 -0
- package/src/scripts/wordpress-security-scan.js +221 -0
- package/src/templates/claude/agents/coolify-deploy.md +563 -0
- package/src/templates/claude/agents/wordpress-admin.md +228 -271
- package/src/templates/claude/commands/myai-configure.md +10 -74
- package/src/templates/claude/commands/myai-coolify-deploy.md +172 -0
- 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;
|