myaidev-method 0.2.22 ā 0.2.23
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/USER_GUIDE.md +453 -48
- package/bin/cli.js +18 -0
- package/content-rules.example.md +80 -0
- package/dist/mcp/mcp-launcher.js +237 -0
- package/dist/server/.tsbuildinfo +1 -1
- package/dist/server/auth/layers.d.ts +1 -1
- package/dist/server/auth/services/AuthService.d.ts +1 -1
- package/dist/server/auth/services/TokenService.js.map +1 -1
- package/dist/server/auth/services/example.d.ts +5 -5
- package/package.json +16 -16
- package/src/index.js +21 -8
- package/src/lib/update-manager.js +2 -1
- package/src/lib/visual-config-utils.js +321 -295
- package/src/lib/visual-generation-utils.js +1000 -811
- package/src/scripts/configure-wordpress-mcp.js +8 -3
- package/src/scripts/generate-visual-cli.js +365 -235
- package/src/scripts/ping.js +250 -0
- package/src/scripts/wordpress/publish-to-wordpress.js +165 -0
- package/src/server/auth/services/TokenService.ts +1 -1
- package/src/templates/claude/agents/content-rules-setup.md +657 -0
- package/src/templates/claude/agents/content-writer.md +328 -1
- package/src/templates/claude/agents/visual-content-generator.md +182 -4
- package/src/templates/claude/commands/myai-configure.md +1 -1
- package/src/templates/claude/commands/myai-content-rules-setup.md +204 -0
- package/src/templates/codex/commands/myai-content-rules-setup.md +85 -0
- package/src/templates/gemini/commands/myai-content-rules-setup.toml +57 -0
- package/.claude/mcp/sparc-orchestrator-server.js +0 -607
- package/.claude/mcp/wordpress-server.js +0 -1277
- package/src/agents/content-writer-prompt.md +0 -164
- package/src/agents/content-writer.json +0 -70
- package/src/templates/claude/mcp_config.json +0 -74
- package/src/templates/claude/slash_commands.json +0 -166
- package/src/templates/scripts/configure-wordpress-mcp.js +0 -181
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { exec } from "child_process";
|
|
4
|
+
import { promisify } from "util";
|
|
5
|
+
import os from "os";
|
|
6
|
+
|
|
7
|
+
const execAsync = promisify(exec);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Location to IP mapping
|
|
11
|
+
*/
|
|
12
|
+
export const LOCATIONS = {
|
|
13
|
+
"ashburn-va": "8.8.8.8", // Google DNS (East Coast reference)
|
|
14
|
+
"los-angeles": "1.1.1.1", // Cloudflare DNS (West Coast reference)
|
|
15
|
+
chicago: "8.8.4.4", // Google DNS secondary
|
|
16
|
+
europe: "1.0.0.1", // Cloudflare secondary
|
|
17
|
+
asia: "208.67.222.222", // OpenDNS
|
|
18
|
+
amsterdam: "213.165.253.121", // aditya's ramnode vm
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get device's local IP
|
|
23
|
+
*/
|
|
24
|
+
function getLocalIP() {
|
|
25
|
+
const interfaces = os.networkInterfaces();
|
|
26
|
+
|
|
27
|
+
for (const name of Object.keys(interfaces)) {
|
|
28
|
+
for (const iface of interfaces[name]) {
|
|
29
|
+
// Skip internal and non-IPv4
|
|
30
|
+
if (iface.family === "IPv4" && !iface.internal) {
|
|
31
|
+
return iface.address;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return "127.0.0.1";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get public IP and location info
|
|
41
|
+
*/
|
|
42
|
+
async function getPublicIPInfo() {
|
|
43
|
+
try {
|
|
44
|
+
// Try ip-api.com first (free, no key needed)
|
|
45
|
+
const response = await fetch("http://ip-api.com/json/");
|
|
46
|
+
const data = await response.json();
|
|
47
|
+
|
|
48
|
+
// Check if we got valid data
|
|
49
|
+
if (data.status === "success" && data.query) {
|
|
50
|
+
return {
|
|
51
|
+
ip: data.query,
|
|
52
|
+
city: data.city || "Unknown City",
|
|
53
|
+
region: data.regionName || data.region,
|
|
54
|
+
country: data.country || "Unknown Country",
|
|
55
|
+
location: `${data.city}, ${data.country}`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Fallback if API returns error
|
|
60
|
+
throw new Error("Invalid response from IP API");
|
|
61
|
+
} catch (error) {
|
|
62
|
+
// Use local IP as fallback
|
|
63
|
+
const localIP = getLocalIP();
|
|
64
|
+
return {
|
|
65
|
+
ip: localIP,
|
|
66
|
+
location: `Local Network (${localIP})`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Simple ping function
|
|
73
|
+
* @param {string} destination - IP address or hostname to ping
|
|
74
|
+
* @param {string} source - Source IP (defaults to device IP)
|
|
75
|
+
* @returns {Promise<object>} Ping results
|
|
76
|
+
*/
|
|
77
|
+
export async function ping(destination, source = null) {
|
|
78
|
+
const sourceIP = source || getLocalIP();
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
const platform = process.platform;
|
|
82
|
+
const countFlag = platform === "win32" ? "-n" : "-c";
|
|
83
|
+
const command = `ping ${countFlag} 4 ${destination}`;
|
|
84
|
+
|
|
85
|
+
const { stdout } = await execAsync(command);
|
|
86
|
+
|
|
87
|
+
// Parse average latency
|
|
88
|
+
let avgLatency = null;
|
|
89
|
+
|
|
90
|
+
if (platform === "win32") {
|
|
91
|
+
const match = stdout.match(/Average = (\d+)ms/);
|
|
92
|
+
if (match) avgLatency = parseFloat(match[1]);
|
|
93
|
+
} else {
|
|
94
|
+
const match = stdout.match(/min\/avg\/max\/[^\s]+ = [\d.]+\/([\d.]+)\//);
|
|
95
|
+
if (match) avgLatency = parseFloat(match[1]);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
source: sourceIP,
|
|
100
|
+
destination,
|
|
101
|
+
latency: avgLatency,
|
|
102
|
+
unit: "ms",
|
|
103
|
+
success: true,
|
|
104
|
+
};
|
|
105
|
+
} catch (error) {
|
|
106
|
+
return {
|
|
107
|
+
source: sourceIP,
|
|
108
|
+
destination,
|
|
109
|
+
latency: null,
|
|
110
|
+
error: error.message,
|
|
111
|
+
success: false,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Ping multiple locations
|
|
118
|
+
* @param {Array<string>} locations - Array of location keys from LOCATIONS
|
|
119
|
+
* @param {string} source - Source IP (optional)
|
|
120
|
+
* @returns {Promise<Array>} Array of ping results
|
|
121
|
+
*/
|
|
122
|
+
export async function pingLocations(locations, source = null) {
|
|
123
|
+
const results = [];
|
|
124
|
+
|
|
125
|
+
for (const loc of locations) {
|
|
126
|
+
const ip = LOCATIONS[loc];
|
|
127
|
+
if (!ip) {
|
|
128
|
+
results.push({
|
|
129
|
+
location: loc,
|
|
130
|
+
error: "Unknown location",
|
|
131
|
+
success: false,
|
|
132
|
+
});
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const result = await ping(ip, source);
|
|
137
|
+
results.push({
|
|
138
|
+
location: loc,
|
|
139
|
+
...result,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return results;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Format ping results as markdown
|
|
148
|
+
* @param {Array} results - Array of ping results from pingLocations
|
|
149
|
+
* @param {object} options - Formatting options
|
|
150
|
+
* @returns {string} Markdown formatted output
|
|
151
|
+
*/
|
|
152
|
+
export async function formatAsMarkdown(results, options = {}) {
|
|
153
|
+
const { includeMetadata = true, title = "Network Latency Test Results" } =
|
|
154
|
+
options;
|
|
155
|
+
|
|
156
|
+
let md = `## ${title}\n\n`;
|
|
157
|
+
|
|
158
|
+
if (includeMetadata && results.length > 0) {
|
|
159
|
+
const timestamp = new Date().toISOString();
|
|
160
|
+
const ipInfo = await getPublicIPInfo();
|
|
161
|
+
|
|
162
|
+
md += `**Test Date**: ${timestamp}\n`;
|
|
163
|
+
md += `**Source**: ${ipInfo.location} (${ipInfo.ip})\n\n`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Build table
|
|
167
|
+
md += "| Location | Destination | Latency | Status |\n";
|
|
168
|
+
md += "|----------|-------------|---------|--------|\n";
|
|
169
|
+
|
|
170
|
+
for (const result of results) {
|
|
171
|
+
const location = result.location || "N/A";
|
|
172
|
+
const destination = result.destination || "N/A";
|
|
173
|
+
const latency = result.success ? `${result.latency}ms` : "-";
|
|
174
|
+
const status = result.success
|
|
175
|
+
? "ā
Success"
|
|
176
|
+
: `ā ${result.error || "Failed"}`;
|
|
177
|
+
|
|
178
|
+
md += `| ${location} | ${destination} | ${latency} | ${status} |\n`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Add summary statistics
|
|
182
|
+
const successful = results.filter((r) => r.success);
|
|
183
|
+
if (successful.length > 0) {
|
|
184
|
+
md += "\n### Summary\n\n";
|
|
185
|
+
|
|
186
|
+
const latencies = successful.map((r) => r.latency);
|
|
187
|
+
const avgLatency = (
|
|
188
|
+
latencies.reduce((a, b) => a + b, 0) / latencies.length
|
|
189
|
+
).toFixed(2);
|
|
190
|
+
const minLatency = Math.min(...latencies).toFixed(2);
|
|
191
|
+
const maxLatency = Math.max(...latencies).toFixed(2);
|
|
192
|
+
|
|
193
|
+
md += `- **Average Latency**: ${avgLatency}ms\n`;
|
|
194
|
+
md += `- **Best Latency**: ${minLatency}ms\n`;
|
|
195
|
+
md += `- **Worst Latency**: ${maxLatency}ms\n`;
|
|
196
|
+
md += `- **Success Rate**: ${successful.length}/${results.length} (${((successful.length / results.length) * 100).toFixed(0)}%)\n`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return md;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// CLI interface
|
|
203
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
204
|
+
const args = process.argv.slice(2);
|
|
205
|
+
|
|
206
|
+
// Check for --all flag
|
|
207
|
+
if (args.includes("--all")) {
|
|
208
|
+
console.log("Testing all locations...\n");
|
|
209
|
+
const allLocations = Object.keys(LOCATIONS);
|
|
210
|
+
const results = await pingLocations(allLocations);
|
|
211
|
+
const markdown = await formatAsMarkdown(results);
|
|
212
|
+
console.log(markdown);
|
|
213
|
+
process.exit(0);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Check for multiple locations
|
|
217
|
+
if (args.length > 1) {
|
|
218
|
+
console.log(`Testing ${args.length} locations...\n`);
|
|
219
|
+
const results = await pingLocations(args);
|
|
220
|
+
const markdown = await formatAsMarkdown(results);
|
|
221
|
+
console.log(markdown);
|
|
222
|
+
process.exit(0);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const destination = args[0];
|
|
226
|
+
|
|
227
|
+
if (!destination) {
|
|
228
|
+
console.log("Usage:");
|
|
229
|
+
console.log(
|
|
230
|
+
" node ping.js <destination> # Single location (markdown output)",
|
|
231
|
+
);
|
|
232
|
+
console.log(
|
|
233
|
+
" node ping.js <loc1> <loc2> <loc3> # Multiple locations (markdown output)",
|
|
234
|
+
);
|
|
235
|
+
console.log(
|
|
236
|
+
" node ping.js --all # Test all locations (markdown output)",
|
|
237
|
+
);
|
|
238
|
+
console.log("\nAvailable locations:");
|
|
239
|
+
Object.keys(LOCATIONS).forEach((loc) => {
|
|
240
|
+
console.log(` ${loc} -> ${LOCATIONS[loc]}`);
|
|
241
|
+
});
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Single location - markdown output (consistent with multiple locations)
|
|
246
|
+
console.log(`Testing ${destination}...\n`);
|
|
247
|
+
const results = await pingLocations([destination]);
|
|
248
|
+
const markdown = await formatAsMarkdown(results);
|
|
249
|
+
console.log(markdown);
|
|
250
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Publish markdown content to WordPress
|
|
4
|
+
*
|
|
5
|
+
* Usage: node publish-to-wordpress.js <file-path> [--status draft|publish]
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readFileSync } from "fs";
|
|
9
|
+
import { resolve } from "path";
|
|
10
|
+
import matter from "gray-matter";
|
|
11
|
+
import dotenv from "dotenv";
|
|
12
|
+
import { marked } from "marked";
|
|
13
|
+
import { GutenbergConverter } from "../../mcp/gutenberg-converter.js";
|
|
14
|
+
import { WordPressMCP } from "../../mcp/wordpress-integration.js";
|
|
15
|
+
|
|
16
|
+
// Load environment variables
|
|
17
|
+
dotenv.config();
|
|
18
|
+
|
|
19
|
+
// Parse command line arguments
|
|
20
|
+
const args = process.argv.slice(2);
|
|
21
|
+
const filePath = args[0];
|
|
22
|
+
const statusIndex = args.indexOf("--status");
|
|
23
|
+
const requestedStatus =
|
|
24
|
+
statusIndex !== -1 && args[statusIndex + 1] ? args[statusIndex + 1] : "draft";
|
|
25
|
+
|
|
26
|
+
if (!filePath) {
|
|
27
|
+
console.error("Error: File path is required");
|
|
28
|
+
console.error(
|
|
29
|
+
"Usage: node publish-to-wordpress.js <file-path> [--status draft|publish]",
|
|
30
|
+
);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Validate WordPress configuration
|
|
35
|
+
if (
|
|
36
|
+
!process.env.WORDPRESS_URL ||
|
|
37
|
+
!process.env.WORDPRESS_USERNAME ||
|
|
38
|
+
!process.env.WORDPRESS_APP_PASSWORD
|
|
39
|
+
) {
|
|
40
|
+
console.error("Error: WordPress configuration is incomplete");
|
|
41
|
+
console.error("Required environment variables:");
|
|
42
|
+
console.error(" - WORDPRESS_URL");
|
|
43
|
+
console.error(" - WORDPRESS_USERNAME");
|
|
44
|
+
console.error(" - WORDPRESS_APP_PASSWORD");
|
|
45
|
+
console.error(
|
|
46
|
+
"\nRun /myai-configure wordpress to set up your WordPress connection",
|
|
47
|
+
);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function publishToWordPress() {
|
|
52
|
+
try {
|
|
53
|
+
// Read and parse markdown file
|
|
54
|
+
const absolutePath = resolve(filePath);
|
|
55
|
+
const fileContent = readFileSync(absolutePath, "utf-8");
|
|
56
|
+
const { data: frontmatter, content } = matter(fileContent);
|
|
57
|
+
|
|
58
|
+
console.log("š Processing file:", filePath);
|
|
59
|
+
console.log("š Title:", frontmatter.title || "No title");
|
|
60
|
+
|
|
61
|
+
// Extract interactive blocks before markdown conversion
|
|
62
|
+
// const { markdown: cleanMarkdown, interactiveBlocks } =
|
|
63
|
+
// GutenbergConverter.extractInteractiveBlocks(content);
|
|
64
|
+
|
|
65
|
+
// if (interactiveBlocks.length > 0) {
|
|
66
|
+
// console.log(
|
|
67
|
+
// ` Interactive blocks: ${interactiveBlocks.length} found`,
|
|
68
|
+
// );
|
|
69
|
+
// }
|
|
70
|
+
|
|
71
|
+
// Convert markdown to HTML using marked (preserves placeholders)
|
|
72
|
+
const html = marked.parse(content);
|
|
73
|
+
|
|
74
|
+
// Convert HTML to Gutenberg blocks
|
|
75
|
+
let htmlContent = GutenbergConverter.htmlToGutenberg(html);
|
|
76
|
+
|
|
77
|
+
// Restore interactive blocks as raw HTML blocks
|
|
78
|
+
// if (interactiveBlocks.length > 0) {
|
|
79
|
+
// htmlContent = GutenbergConverter.restoreInteractiveBlocks(
|
|
80
|
+
// htmlContent,
|
|
81
|
+
// interactiveBlocks,
|
|
82
|
+
// );
|
|
83
|
+
// }
|
|
84
|
+
|
|
85
|
+
// Prepare WordPress configuration
|
|
86
|
+
const config = {
|
|
87
|
+
endpoint: {
|
|
88
|
+
url: process.env.WORDPRESS_URL,
|
|
89
|
+
api_version: "wp/v2",
|
|
90
|
+
authentication: {
|
|
91
|
+
username: process.env.WORDPRESS_USERNAME,
|
|
92
|
+
password: process.env.WORDPRESS_APP_PASSWORD,
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
defaults: {
|
|
96
|
+
post_status: requestedStatus,
|
|
97
|
+
format: "standard",
|
|
98
|
+
comment_status: "open",
|
|
99
|
+
ping_status: "open",
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Initialize WordPress client
|
|
104
|
+
const wp = new WordPressMCP(config);
|
|
105
|
+
|
|
106
|
+
// Prepare post data
|
|
107
|
+
const postParams = {
|
|
108
|
+
title: frontmatter.title || "Untitled",
|
|
109
|
+
content: htmlContent,
|
|
110
|
+
status: requestedStatus,
|
|
111
|
+
excerpt: frontmatter.meta_description || "",
|
|
112
|
+
slug: frontmatter.slug || "",
|
|
113
|
+
use_gutenberg: process.env.USE_GUTENBERG === "true",
|
|
114
|
+
// Note: Tags and categories require ID mapping which will be added in future versions
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
console.log("š Publishing to WordPress...");
|
|
118
|
+
console.log(" URL:", process.env.WORDPRESS_URL);
|
|
119
|
+
console.log(" Status:", requestedStatus);
|
|
120
|
+
console.log(
|
|
121
|
+
" Format:",
|
|
122
|
+
postParams.use_gutenberg ? "Gutenberg" : "Classic",
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
// Create the post
|
|
126
|
+
const result = await wp.createPost(postParams);
|
|
127
|
+
|
|
128
|
+
// Success!
|
|
129
|
+
console.log("\nā
Successfully published to WordPress!");
|
|
130
|
+
console.log("\nš Post Details:");
|
|
131
|
+
console.log(" Post ID:", result.id);
|
|
132
|
+
console.log(" Title:", result.title.rendered);
|
|
133
|
+
console.log(" Status:", result.status);
|
|
134
|
+
console.log(" Slug:", result.slug);
|
|
135
|
+
|
|
136
|
+
console.log("\nš URLs:");
|
|
137
|
+
console.log(" View:", result.link);
|
|
138
|
+
console.log(
|
|
139
|
+
" Edit:",
|
|
140
|
+
`${process.env.WORDPRESS_URL}/wp-admin/post.php?post=${result.id}&action=edit`,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
return result;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
console.error("\nā Error publishing to WordPress:");
|
|
146
|
+
console.error(error.message);
|
|
147
|
+
|
|
148
|
+
if (error.message.includes("401")) {
|
|
149
|
+
console.error("\nš Authentication failed. Please check:");
|
|
150
|
+
console.error(" 1. Your WordPress username is correct");
|
|
151
|
+
console.error(" 2. Your Application Password is valid");
|
|
152
|
+
console.error(" 3. Application Passwords are enabled on your site");
|
|
153
|
+
} else if (error.message.includes("404")) {
|
|
154
|
+
console.error("\nš WordPress site not found. Please check:");
|
|
155
|
+
console.error(" 1. Your WORDPRESS_URL is correct");
|
|
156
|
+
console.error(" 2. The site is accessible");
|
|
157
|
+
console.error(" 3. The REST API is enabled");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Run the publisher
|
|
165
|
+
publishToWordPress();
|
|
@@ -6,7 +6,7 @@ import { AuthError, JWTPayload } from "../../../shared/types.js";
|
|
|
6
6
|
const TOKEN_EXPIRY_DAYS = 7;
|
|
7
7
|
const ALGORITHM = "RS256";
|
|
8
8
|
|
|
9
|
-
let keyPair: { publicKey: jose.
|
|
9
|
+
let keyPair: { publicKey: jose.CryptoKey; privateKey: jose.CryptoKey } | null = null;
|
|
10
10
|
|
|
11
11
|
export class TokenService extends Context.Tag("TokenService")<
|
|
12
12
|
TokenService,
|