myaidev-method 0.0.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/.env.example +9 -0
- package/LICENSE +21 -0
- package/README.md +744 -0
- package/bin/cli.js +228 -0
- package/package.json +59 -0
- package/src/agents/content-writer-prompt.md +164 -0
- package/src/agents/content-writer.json +70 -0
- package/src/index.js +21 -0
- package/src/mcp/ssh-integration.js +488 -0
- package/src/mcp/wordpress-admin-mcp.js +397 -0
- package/src/mcp/wordpress-integration.js +218 -0
- package/src/mcp/wordpress-mcp.json +148 -0
- package/src/templates/claude/agents/content-writer.md +155 -0
- package/src/templates/claude/agents/wordpress-admin.md +240 -0
- package/src/templates/claude/commands/myai-configure.md +104 -0
- package/src/templates/claude/commands/myai-content-writer.md +78 -0
- package/src/templates/claude/commands/myai-wordpress-admin.md +148 -0
- package/src/templates/claude/commands/myai-wordpress-publish.md +55 -0
- package/src/templates/claude/mcp_config.json +30 -0
- package/src/templates/claude/slash_commands.json +166 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import { Client } from 'ssh2';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export class SSHWordPressManager {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.host = config.host;
|
|
8
|
+
this.username = config.username;
|
|
9
|
+
this.privateKeyPath = config.privateKeyPath;
|
|
10
|
+
this.password = config.password; // Alternative to key auth
|
|
11
|
+
this.port = config.port || 22;
|
|
12
|
+
this.wordpressPath = config.wordpressPath || '/var/www/html';
|
|
13
|
+
this.connection = null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async connect() {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
this.connection = new Client();
|
|
19
|
+
|
|
20
|
+
const authConfig = {
|
|
21
|
+
host: this.host,
|
|
22
|
+
port: this.port,
|
|
23
|
+
username: this.username
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Use private key if available, otherwise password
|
|
27
|
+
if (this.privateKeyPath && fs.existsSync(this.privateKeyPath)) {
|
|
28
|
+
authConfig.privateKey = fs.readFileSync(this.privateKeyPath);
|
|
29
|
+
} else if (this.password) {
|
|
30
|
+
authConfig.password = this.password;
|
|
31
|
+
} else {
|
|
32
|
+
reject(new Error('No valid authentication method provided'));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.connection.on('ready', () => {
|
|
37
|
+
resolve(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
this.connection.on('error', (err) => {
|
|
41
|
+
reject(err);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
this.connection.connect(authConfig);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async executeCommand(command, options = {}) {
|
|
49
|
+
if (!this.connection) {
|
|
50
|
+
throw new Error('SSH connection not established');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
this.connection.exec(command, (err, stream) => {
|
|
55
|
+
if (err) {
|
|
56
|
+
reject(err);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let stdout = '';
|
|
61
|
+
let stderr = '';
|
|
62
|
+
|
|
63
|
+
stream.on('close', (code, signal) => {
|
|
64
|
+
resolve({
|
|
65
|
+
code,
|
|
66
|
+
signal,
|
|
67
|
+
stdout: stdout.trim(),
|
|
68
|
+
stderr: stderr.trim(),
|
|
69
|
+
success: code === 0
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
stream.on('data', (data) => {
|
|
74
|
+
stdout += data.toString();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
stream.stderr.on('data', (data) => {
|
|
78
|
+
stderr += data.toString();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (options.timeout) {
|
|
82
|
+
setTimeout(() => {
|
|
83
|
+
stream.close();
|
|
84
|
+
reject(new Error(`Command timeout after ${options.timeout}ms`));
|
|
85
|
+
}, options.timeout);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async disconnect() {
|
|
92
|
+
if (this.connection) {
|
|
93
|
+
this.connection.end();
|
|
94
|
+
this.connection = null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// WordPress-specific server operations
|
|
99
|
+
async getServerInfo() {
|
|
100
|
+
try {
|
|
101
|
+
const commands = [
|
|
102
|
+
'uname -a', // OS info
|
|
103
|
+
'php -v', // PHP version
|
|
104
|
+
'mysql --version', // MySQL version
|
|
105
|
+
'df -h', // Disk usage
|
|
106
|
+
'free -m', // Memory usage
|
|
107
|
+
'uptime', // Server uptime
|
|
108
|
+
'nginx -v 2>&1 || apache2 -v', // Web server version
|
|
109
|
+
];
|
|
110
|
+
|
|
111
|
+
const results = {};
|
|
112
|
+
for (const cmd of commands) {
|
|
113
|
+
const result = await this.executeCommand(cmd);
|
|
114
|
+
results[cmd] = result;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
success: true,
|
|
119
|
+
server_info: this.parseServerInfo(results),
|
|
120
|
+
raw_results: results
|
|
121
|
+
};
|
|
122
|
+
} catch (error) {
|
|
123
|
+
return {
|
|
124
|
+
success: false,
|
|
125
|
+
error: error.message
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async checkFilePermissions() {
|
|
131
|
+
try {
|
|
132
|
+
const commands = [
|
|
133
|
+
`find ${this.wordpressPath} -type f -perm 777`,
|
|
134
|
+
`find ${this.wordpressPath} -type d -perm 777`,
|
|
135
|
+
`ls -la ${this.wordpressPath}/wp-config.php`,
|
|
136
|
+
`ls -la ${this.wordpressPath}/wp-content/uploads/`,
|
|
137
|
+
`find ${this.wordpressPath}/wp-content/ -name "*.php" -perm +200`
|
|
138
|
+
];
|
|
139
|
+
|
|
140
|
+
const results = {};
|
|
141
|
+
for (const cmd of commands) {
|
|
142
|
+
results[cmd] = await this.executeCommand(cmd);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
success: true,
|
|
147
|
+
permission_check: this.analyzePermissions(results),
|
|
148
|
+
recommendations: this.getPermissionRecommendations(results)
|
|
149
|
+
};
|
|
150
|
+
} catch (error) {
|
|
151
|
+
return {
|
|
152
|
+
success: false,
|
|
153
|
+
error: error.message
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async scanForMalware() {
|
|
159
|
+
try {
|
|
160
|
+
const suspiciousPatterns = [
|
|
161
|
+
'eval\\(',
|
|
162
|
+
'base64_decode\\(',
|
|
163
|
+
'shell_exec\\(',
|
|
164
|
+
'system\\(',
|
|
165
|
+
'exec\\(',
|
|
166
|
+
'passthru\\(',
|
|
167
|
+
'c99shell',
|
|
168
|
+
'r57shell'
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
const scanCommands = suspiciousPatterns.map(pattern =>
|
|
172
|
+
`grep -r "${pattern}" ${this.wordpressPath}/wp-content/ --include="*.php" || true`
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const results = [];
|
|
176
|
+
for (const cmd of scanCommands) {
|
|
177
|
+
const result = await this.executeCommand(cmd);
|
|
178
|
+
if (result.stdout) {
|
|
179
|
+
results.push({
|
|
180
|
+
pattern: cmd.match(/"(.+?)"/)[1],
|
|
181
|
+
matches: result.stdout.split('\n').filter(line => line.trim())
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
success: true,
|
|
188
|
+
malware_scan: {
|
|
189
|
+
timestamp: new Date().toISOString(),
|
|
190
|
+
suspicious_files: results,
|
|
191
|
+
total_matches: results.reduce((sum, r) => sum + r.matches.length, 0)
|
|
192
|
+
},
|
|
193
|
+
recommendations: this.getMalwareRecommendations(results)
|
|
194
|
+
};
|
|
195
|
+
} catch (error) {
|
|
196
|
+
return {
|
|
197
|
+
success: false,
|
|
198
|
+
error: error.message
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async analyzeErrorLogs() {
|
|
204
|
+
try {
|
|
205
|
+
const logPaths = [
|
|
206
|
+
'/var/log/nginx/error.log',
|
|
207
|
+
'/var/log/apache2/error.log',
|
|
208
|
+
'/var/log/php_errors.log',
|
|
209
|
+
`${this.wordpressPath}/wp-content/debug.log`,
|
|
210
|
+
'/var/log/mysql/error.log'
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
const results = {};
|
|
214
|
+
for (const logPath of logPaths) {
|
|
215
|
+
const cmd = `tail -n 100 ${logPath} 2>/dev/null || echo "Log file not found: ${logPath}"`;
|
|
216
|
+
results[logPath] = await this.executeCommand(cmd);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
success: true,
|
|
221
|
+
error_analysis: this.parseErrorLogs(results),
|
|
222
|
+
recommendations: this.getErrorLogRecommendations(results)
|
|
223
|
+
};
|
|
224
|
+
} catch (error) {
|
|
225
|
+
return {
|
|
226
|
+
success: false,
|
|
227
|
+
error: error.message
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async optimizeDatabase() {
|
|
233
|
+
try {
|
|
234
|
+
// Get WordPress database credentials
|
|
235
|
+
const configCmd = `grep -E "DB_(NAME|USER|PASSWORD|HOST)" ${this.wordpressPath}/wp-config.php`;
|
|
236
|
+
const configResult = await this.executeCommand(configCmd);
|
|
237
|
+
|
|
238
|
+
if (!configResult.success) {
|
|
239
|
+
throw new Error('Could not read WordPress database configuration');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const dbConfig = this.parseDbConfig(configResult.stdout);
|
|
243
|
+
|
|
244
|
+
// Database optimization commands
|
|
245
|
+
const optimizationCommands = [
|
|
246
|
+
`mysql -h${dbConfig.host} -u${dbConfig.user} -p${dbConfig.password} ${dbConfig.name} -e "OPTIMIZE TABLE wp_posts, wp_postmeta, wp_options, wp_comments, wp_commentmeta;"`,
|
|
247
|
+
`mysql -h${dbConfig.host} -u${dbConfig.user} -p${dbConfig.password} ${dbConfig.name} -e "DELETE FROM wp_posts WHERE post_status = 'revision' AND post_date < DATE_SUB(NOW(), INTERVAL 30 DAY);"`,
|
|
248
|
+
`mysql -h${dbConfig.host} -u${dbConfig.user} -p${dbConfig.password} ${dbConfig.name} -e "DELETE FROM wp_comments WHERE comment_approved = 'spam' AND comment_date < DATE_SUB(NOW(), INTERVAL 30 DAY);"`
|
|
249
|
+
];
|
|
250
|
+
|
|
251
|
+
const results = {};
|
|
252
|
+
for (const cmd of optimizationCommands) {
|
|
253
|
+
results[cmd] = await this.executeCommand(cmd);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return {
|
|
257
|
+
success: true,
|
|
258
|
+
database_optimization: results,
|
|
259
|
+
summary: this.summarizeDatabaseOptimization(results)
|
|
260
|
+
};
|
|
261
|
+
} catch (error) {
|
|
262
|
+
return {
|
|
263
|
+
success: false,
|
|
264
|
+
error: error.message
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async createBackup() {
|
|
270
|
+
try {
|
|
271
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
272
|
+
const backupDir = `/tmp/wordpress-backup-${timestamp}`;
|
|
273
|
+
|
|
274
|
+
const commands = [
|
|
275
|
+
`mkdir -p ${backupDir}`,
|
|
276
|
+
`tar -czf ${backupDir}/wordpress-files.tar.gz -C ${path.dirname(this.wordpressPath)} ${path.basename(this.wordpressPath)}`,
|
|
277
|
+
`mysqldump --single-transaction --routines --triggers $(grep DB_NAME ${this.wordpressPath}/wp-config.php | cut -d "'" -f 4) > ${backupDir}/database.sql`
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
const results = {};
|
|
281
|
+
for (const cmd of commands) {
|
|
282
|
+
results[cmd] = await this.executeCommand(cmd);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
success: true,
|
|
287
|
+
backup_location: backupDir,
|
|
288
|
+
backup_files: [`${backupDir}/wordpress-files.tar.gz`, `${backupDir}/database.sql`],
|
|
289
|
+
timestamp: timestamp,
|
|
290
|
+
commands_executed: results
|
|
291
|
+
};
|
|
292
|
+
} catch (error) {
|
|
293
|
+
return {
|
|
294
|
+
success: false,
|
|
295
|
+
error: error.message
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async updateWordPressFiles() {
|
|
301
|
+
try {
|
|
302
|
+
const commands = [
|
|
303
|
+
`cd ${this.wordpressPath} && wp core update --allow-root`,
|
|
304
|
+
`cd ${this.wordpressPath} && wp plugin update --all --allow-root`,
|
|
305
|
+
`cd ${this.wordpressPath} && wp theme update --all --allow-root`
|
|
306
|
+
];
|
|
307
|
+
|
|
308
|
+
const results = {};
|
|
309
|
+
for (const cmd of commands) {
|
|
310
|
+
results[cmd] = await this.executeCommand(cmd);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return {
|
|
314
|
+
success: true,
|
|
315
|
+
update_results: results,
|
|
316
|
+
summary: this.summarizeUpdates(results)
|
|
317
|
+
};
|
|
318
|
+
} catch (error) {
|
|
319
|
+
return {
|
|
320
|
+
success: false,
|
|
321
|
+
error: error.message
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Helper methods
|
|
327
|
+
parseServerInfo(results) {
|
|
328
|
+
return {
|
|
329
|
+
os: results['uname -a']?.stdout || 'Unknown',
|
|
330
|
+
php_version: this.extractPhpVersion(results['php -v']?.stdout),
|
|
331
|
+
mysql_version: this.extractMysqlVersion(results['mysql --version']?.stdout),
|
|
332
|
+
disk_usage: results['df -h']?.stdout,
|
|
333
|
+
memory_usage: results['free -m']?.stdout,
|
|
334
|
+
uptime: results['uptime']?.stdout,
|
|
335
|
+
web_server: results['nginx -v 2>&1 || apache2 -v']?.stdout
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
extractPhpVersion(phpOutput) {
|
|
340
|
+
if (!phpOutput) return 'Unknown';
|
|
341
|
+
const match = phpOutput.match(/PHP (\d+\.\d+\.\d+)/);
|
|
342
|
+
return match ? match[1] : 'Unknown';
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
extractMysqlVersion(mysqlOutput) {
|
|
346
|
+
if (!mysqlOutput) return 'Unknown';
|
|
347
|
+
const match = mysqlOutput.match(/(\d+\.\d+\.\d+)/);
|
|
348
|
+
return match ? match[1] : 'Unknown';
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
analyzePermissions(results) {
|
|
352
|
+
const issues = [];
|
|
353
|
+
|
|
354
|
+
Object.entries(results).forEach(([cmd, result]) => {
|
|
355
|
+
if (result.stdout && result.stdout.trim()) {
|
|
356
|
+
if (cmd.includes('perm 777')) {
|
|
357
|
+
issues.push({
|
|
358
|
+
type: 'overly_permissive',
|
|
359
|
+
files: result.stdout.split('\n'),
|
|
360
|
+
severity: 'high'
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
total_issues: issues.length,
|
|
368
|
+
issues: issues,
|
|
369
|
+
status: issues.length === 0 ? 'good' : 'needs_attention'
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
getPermissionRecommendations(results) {
|
|
374
|
+
const recommendations = [];
|
|
375
|
+
|
|
376
|
+
// Add specific recommendations based on findings
|
|
377
|
+
recommendations.push({
|
|
378
|
+
action: 'Set proper file permissions',
|
|
379
|
+
command: `find ${this.wordpressPath} -type f -exec chmod 644 {} \\;`,
|
|
380
|
+
description: 'Set all files to 644 permissions'
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
recommendations.push({
|
|
384
|
+
action: 'Set proper directory permissions',
|
|
385
|
+
command: `find ${this.wordpressPath} -type d -exec chmod 755 {} \\;`,
|
|
386
|
+
description: 'Set all directories to 755 permissions'
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
return recommendations;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
parseDbConfig(configOutput) {
|
|
393
|
+
const config = {};
|
|
394
|
+
const lines = configOutput.split('\n');
|
|
395
|
+
|
|
396
|
+
lines.forEach(line => {
|
|
397
|
+
if (line.includes('DB_NAME')) {
|
|
398
|
+
config.name = line.match(/'([^']+)'/)[1];
|
|
399
|
+
} else if (line.includes('DB_USER')) {
|
|
400
|
+
config.user = line.match(/'([^']+)'/)[1];
|
|
401
|
+
} else if (line.includes('DB_PASSWORD')) {
|
|
402
|
+
config.password = line.match(/'([^']+)'/)[1];
|
|
403
|
+
} else if (line.includes('DB_HOST')) {
|
|
404
|
+
config.host = line.match(/'([^']+)'/)[1];
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
return config;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
parseErrorLogs(results) {
|
|
412
|
+
const analysis = {
|
|
413
|
+
total_errors: 0,
|
|
414
|
+
error_types: {},
|
|
415
|
+
recent_errors: [],
|
|
416
|
+
recommendations: []
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
Object.entries(results).forEach(([logPath, result]) => {
|
|
420
|
+
if (result.stdout && !result.stdout.includes('Log file not found')) {
|
|
421
|
+
const lines = result.stdout.split('\n');
|
|
422
|
+
analysis.total_errors += lines.length;
|
|
423
|
+
|
|
424
|
+
lines.forEach(line => {
|
|
425
|
+
if (line.trim()) {
|
|
426
|
+
analysis.recent_errors.push({
|
|
427
|
+
log: logPath,
|
|
428
|
+
message: line.trim()
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
return analysis;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
getMalwareRecommendations(results) {
|
|
439
|
+
const recommendations = [];
|
|
440
|
+
|
|
441
|
+
if (results.length > 0) {
|
|
442
|
+
recommendations.push({
|
|
443
|
+
priority: 'critical',
|
|
444
|
+
action: 'Quarantine suspicious files',
|
|
445
|
+
description: 'Move suspicious files to quarantine for manual review'
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
recommendations.push({
|
|
449
|
+
priority: 'high',
|
|
450
|
+
action: 'Run full malware scan',
|
|
451
|
+
description: 'Use professional malware scanning tools'
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return recommendations;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
getErrorLogRecommendations(results) {
|
|
459
|
+
return [
|
|
460
|
+
{
|
|
461
|
+
action: 'Monitor error logs regularly',
|
|
462
|
+
description: 'Set up automated error log monitoring'
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
action: 'Configure log rotation',
|
|
466
|
+
description: 'Prevent log files from growing too large'
|
|
467
|
+
}
|
|
468
|
+
];
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
summarizeDatabaseOptimization(results) {
|
|
472
|
+
return {
|
|
473
|
+
tables_optimized: 'Core WordPress tables',
|
|
474
|
+
revisions_cleaned: 'Posts older than 30 days',
|
|
475
|
+
spam_removed: 'Comments older than 30 days'
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
summarizeUpdates(results) {
|
|
480
|
+
return {
|
|
481
|
+
core_updated: results[Object.keys(results)[0]]?.success || false,
|
|
482
|
+
plugins_updated: results[Object.keys(results)[1]]?.success || false,
|
|
483
|
+
themes_updated: results[Object.keys(results)[2]]?.success || false
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export default SSHWordPressManager;
|