myaidev-method 0.2.22 → 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.
- package/USER_GUIDE.md +453 -48
- package/bin/cli.js +236 -38
- 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 +22 -17
- package/src/config/workflows.js +28 -44
- package/src/index.js +21 -8
- package/src/lib/ascii-banner.js +214 -0
- package/src/lib/config-manager.js +470 -0
- package/src/lib/content-generator.js +427 -0
- package/src/lib/html-conversion-utils.js +843 -0
- package/src/lib/seo-optimizer.js +515 -0
- 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/lib/wordpress-client.js +633 -0
- package/src/lib/workflow-installer.js +3 -3
- package/src/scripts/configure-wordpress-mcp.js +8 -3
- package/src/scripts/generate-visual-cli.js +365 -235
- package/src/scripts/html-conversion-cli.js +526 -0
- package/src/scripts/init/configure.js +436 -0
- package/src/scripts/init/install.js +460 -0
- package/src/scripts/ping.js +250 -0
- package/src/scripts/utils/file-utils.js +404 -0
- package/src/scripts/utils/logger.js +300 -0
- package/src/scripts/utils/write-content.js +293 -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 +311 -8
- 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/claude/commands/myai-convert-html.md +186 -0
- package/src/templates/codex/commands/myai-content-rules-setup.md +85 -0
- package/src/templates/diagrams/architecture.d2 +52 -0
- package/src/templates/diagrams/flowchart.d2 +42 -0
- package/src/templates/diagrams/sequence.d2 +47 -0
- package/src/templates/docs/content-creation-guide.md +164 -0
- package/src/templates/docs/deployment-guide.md +336 -0
- package/src/templates/docs/visual-generation-guide.md +248 -0
- package/src/templates/docs/wordpress-publishing-guide.md +208 -0
- package/src/templates/gemini/commands/myai-content-rules-setup.toml +57 -0
- package/src/templates/infographics/comparison-table.html +347 -0
- package/src/templates/infographics/data-chart.html +268 -0
- package/src/templates/infographics/process-flow.html +365 -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
- /package/src/scripts/{wordpress-health-check.js → wordpress/wordpress-health-check.js} +0 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Install Script
|
|
5
|
+
* Workflow installation orchestrator for MyAIDev Method
|
|
6
|
+
* Handles installation of workflows, agents, commands, and dependencies
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import WorkflowInstaller from '../../lib/workflow-installer.js';
|
|
10
|
+
import { ConfigManager } from '../../lib/config-manager.js';
|
|
11
|
+
import { logger, ProgressTracker, createSpinner } from '../utils/logger.js';
|
|
12
|
+
import { ensureDir } from '../utils/file-utils.js';
|
|
13
|
+
import inquirer from 'inquirer';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import fs from 'fs-extra';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Installation Orchestrator Class
|
|
19
|
+
*/
|
|
20
|
+
class InstallationOrchestrator {
|
|
21
|
+
constructor(targetDir = process.cwd()) {
|
|
22
|
+
this.targetDir = targetDir;
|
|
23
|
+
this.configManager = new ConfigManager(targetDir);
|
|
24
|
+
this.installer = new WorkflowInstaller();
|
|
25
|
+
this.installedWorkflows = [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Run the installation
|
|
30
|
+
* @param {Object} options - Installation options
|
|
31
|
+
*/
|
|
32
|
+
async run(options = {}) {
|
|
33
|
+
const {
|
|
34
|
+
workflows = [],
|
|
35
|
+
cliType = 'claude',
|
|
36
|
+
interactive = false,
|
|
37
|
+
force = false,
|
|
38
|
+
verbose = false
|
|
39
|
+
} = options;
|
|
40
|
+
|
|
41
|
+
logger.header('MyAIDev Method Installation');
|
|
42
|
+
logger.info(`Target directory: ${this.targetDir}`);
|
|
43
|
+
logger.info(`CLI type: ${cliType}`);
|
|
44
|
+
logger.blank();
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// Interactive mode - show workflow selection
|
|
48
|
+
if (interactive) {
|
|
49
|
+
const selectedWorkflows = await this.selectWorkflows();
|
|
50
|
+
if (selectedWorkflows.length === 0) {
|
|
51
|
+
logger.info('No workflows selected. Exiting.');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
workflows.push(...selectedWorkflows);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// If no workflows specified, install core
|
|
58
|
+
if (workflows.length === 0) {
|
|
59
|
+
workflows.push('core');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Install each workflow
|
|
63
|
+
const progress = new ProgressTracker(workflows.length, 'Installation');
|
|
64
|
+
progress.start();
|
|
65
|
+
|
|
66
|
+
for (const workflow of workflows) {
|
|
67
|
+
try {
|
|
68
|
+
await this.installWorkflow(workflow, cliType, { force, verbose });
|
|
69
|
+
progress.increment(workflow);
|
|
70
|
+
this.installedWorkflows.push(workflow);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
progress.fail(workflow, error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
progress.complete();
|
|
77
|
+
|
|
78
|
+
// Show summary
|
|
79
|
+
await this.showSummary();
|
|
80
|
+
|
|
81
|
+
// Offer to configure
|
|
82
|
+
if (interactive && this.needsConfiguration()) {
|
|
83
|
+
await this.offerConfiguration();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
} catch (error) {
|
|
87
|
+
logger.error(`Installation failed: ${error.message}`);
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Show workflow selection prompt
|
|
94
|
+
* @returns {Promise<string[]>} Selected workflows
|
|
95
|
+
*/
|
|
96
|
+
async selectWorkflows() {
|
|
97
|
+
const available = this.installer.getAvailableWorkflows();
|
|
98
|
+
|
|
99
|
+
const { workflows } = await inquirer.prompt([
|
|
100
|
+
{
|
|
101
|
+
type: 'checkbox',
|
|
102
|
+
name: 'workflows',
|
|
103
|
+
message: 'Select workflows to install:',
|
|
104
|
+
choices: available.map(w => ({
|
|
105
|
+
name: `${w.name} - ${w.description}`,
|
|
106
|
+
value: w.id,
|
|
107
|
+
checked: w.id === 'core'
|
|
108
|
+
})),
|
|
109
|
+
validate: (answer) => {
|
|
110
|
+
if (answer.length === 0) {
|
|
111
|
+
return 'Please select at least one workflow';
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
]);
|
|
117
|
+
|
|
118
|
+
return workflows;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Install a single workflow
|
|
123
|
+
* @param {string} workflow - Workflow ID
|
|
124
|
+
* @param {string} cliType - CLI type
|
|
125
|
+
* @param {Object} options - Installation options
|
|
126
|
+
*/
|
|
127
|
+
async installWorkflow(workflow, cliType, options = {}) {
|
|
128
|
+
const { verbose = false } = options;
|
|
129
|
+
|
|
130
|
+
if (verbose) {
|
|
131
|
+
logger.info(`Installing workflow: ${workflow}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const spinner = createSpinner(`Installing ${workflow}...`);
|
|
135
|
+
spinner.start();
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const result = await this.installer.install(workflow, this.targetDir, cliType);
|
|
139
|
+
|
|
140
|
+
if (result.success) {
|
|
141
|
+
spinner.succeed(`${workflow} installed`);
|
|
142
|
+
|
|
143
|
+
if (verbose && result.installed) {
|
|
144
|
+
if (result.installed.agents?.length > 0) {
|
|
145
|
+
logger.info(` Agents: ${result.installed.agents.join(', ')}`);
|
|
146
|
+
}
|
|
147
|
+
if (result.installed.commands?.length > 0) {
|
|
148
|
+
logger.info(` Commands: ${result.installed.commands.join(', ')}`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
spinner.fail(`${workflow} failed`);
|
|
153
|
+
if (result.errors) {
|
|
154
|
+
for (const error of result.errors) {
|
|
155
|
+
logger.error(` ${error}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return result;
|
|
161
|
+
} catch (error) {
|
|
162
|
+
spinner.fail(`${workflow} error: ${error.message}`);
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Show installation summary
|
|
169
|
+
*/
|
|
170
|
+
async showSummary() {
|
|
171
|
+
logger.section('Installation Summary');
|
|
172
|
+
|
|
173
|
+
// Count installed items
|
|
174
|
+
const claudeDir = path.join(this.targetDir, '.claude');
|
|
175
|
+
const agentsDir = path.join(claudeDir, 'agents');
|
|
176
|
+
const commandsDir = path.join(claudeDir, 'commands');
|
|
177
|
+
|
|
178
|
+
let agentCount = 0;
|
|
179
|
+
let commandCount = 0;
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
if (await fs.pathExists(agentsDir)) {
|
|
183
|
+
const agents = await fs.readdir(agentsDir);
|
|
184
|
+
agentCount = agents.filter(f => f.endsWith('.md')).length;
|
|
185
|
+
}
|
|
186
|
+
if (await fs.pathExists(commandsDir)) {
|
|
187
|
+
const commands = await fs.readdir(commandsDir);
|
|
188
|
+
commandCount = commands.filter(f => f.endsWith('.md')).length;
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
// Ignore errors
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
logger.log(`Workflows installed: ${this.installedWorkflows.length}`);
|
|
195
|
+
logger.log(` - ${this.installedWorkflows.join(', ')}`);
|
|
196
|
+
logger.blank();
|
|
197
|
+
logger.log(`Agents: ${agentCount}`);
|
|
198
|
+
logger.log(`Commands: ${commandCount}`);
|
|
199
|
+
logger.blank();
|
|
200
|
+
|
|
201
|
+
// Show next steps
|
|
202
|
+
logger.section('Next Steps');
|
|
203
|
+
logger.log('1. Configure your settings:');
|
|
204
|
+
logger.log(' Run: /myai-configure');
|
|
205
|
+
logger.blank();
|
|
206
|
+
logger.log('2. Available commands:');
|
|
207
|
+
|
|
208
|
+
if (this.installedWorkflows.includes('content') || this.installedWorkflows.includes('core')) {
|
|
209
|
+
logger.log(' /myai-content-writer - Create SEO-optimized content');
|
|
210
|
+
}
|
|
211
|
+
if (this.installedWorkflows.includes('content') || this.installedWorkflows.includes('publish-wordpress')) {
|
|
212
|
+
logger.log(' /myai-wordpress-publish - Publish to WordPress');
|
|
213
|
+
}
|
|
214
|
+
if (this.installedWorkflows.includes('content') || this.installedWorkflows.includes('core')) {
|
|
215
|
+
logger.log(' /myai-coordinate-content - Coordinate content workflow');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
logger.blank();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Check if configuration is needed
|
|
223
|
+
* @returns {boolean}
|
|
224
|
+
*/
|
|
225
|
+
needsConfiguration() {
|
|
226
|
+
const needsWordPress = this.installedWorkflows.some(w =>
|
|
227
|
+
['content', 'publish-wordpress'].includes(w)
|
|
228
|
+
);
|
|
229
|
+
const needsOpenStack = this.installedWorkflows.some(w =>
|
|
230
|
+
['openstack'].includes(w)
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
return needsWordPress || needsOpenStack;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Offer to run configuration
|
|
238
|
+
*/
|
|
239
|
+
async offerConfiguration() {
|
|
240
|
+
const { configure } = await inquirer.prompt([
|
|
241
|
+
{
|
|
242
|
+
type: 'confirm',
|
|
243
|
+
name: 'configure',
|
|
244
|
+
message: 'Would you like to configure your settings now?',
|
|
245
|
+
default: true
|
|
246
|
+
}
|
|
247
|
+
]);
|
|
248
|
+
|
|
249
|
+
if (configure) {
|
|
250
|
+
// Import and run configure script
|
|
251
|
+
const { default: runConfigure } = await import('./configure.js');
|
|
252
|
+
// Note: This will run the configure wizard
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Uninstall workflows
|
|
258
|
+
* @param {string[]} workflows - Workflows to uninstall
|
|
259
|
+
*/
|
|
260
|
+
async uninstall(workflows) {
|
|
261
|
+
logger.header('Uninstalling Workflows');
|
|
262
|
+
|
|
263
|
+
for (const workflow of workflows) {
|
|
264
|
+
const spinner = createSpinner(`Uninstalling ${workflow}...`);
|
|
265
|
+
spinner.start();
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
// Get workflow info
|
|
269
|
+
const workflowInfo = this.installer.getWorkflow(workflow);
|
|
270
|
+
if (!workflowInfo) {
|
|
271
|
+
spinner.warn(`Workflow not found: ${workflow}`);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Remove agents
|
|
276
|
+
if (workflowInfo.agents) {
|
|
277
|
+
for (const agent of workflowInfo.agents) {
|
|
278
|
+
const agentPath = path.join(this.targetDir, '.claude', 'agents', `${agent}.md`);
|
|
279
|
+
await fs.remove(agentPath);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Remove commands
|
|
284
|
+
if (workflowInfo.commands) {
|
|
285
|
+
for (const command of workflowInfo.commands) {
|
|
286
|
+
const commandPath = path.join(this.targetDir, '.claude', 'commands', `${command}.md`);
|
|
287
|
+
await fs.remove(commandPath);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
spinner.succeed(`${workflow} uninstalled`);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
spinner.fail(`Failed to uninstall ${workflow}: ${error.message}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* List installed workflows
|
|
300
|
+
*/
|
|
301
|
+
async listInstalled() {
|
|
302
|
+
logger.header('Installed Workflows');
|
|
303
|
+
|
|
304
|
+
const claudeDir = path.join(this.targetDir, '.claude');
|
|
305
|
+
|
|
306
|
+
if (!await fs.pathExists(claudeDir)) {
|
|
307
|
+
logger.info('No workflows installed.');
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const agentsDir = path.join(claudeDir, 'agents');
|
|
312
|
+
const commandsDir = path.join(claudeDir, 'commands');
|
|
313
|
+
|
|
314
|
+
logger.section('Agents');
|
|
315
|
+
if (await fs.pathExists(agentsDir)) {
|
|
316
|
+
const agents = await fs.readdir(agentsDir);
|
|
317
|
+
const mdAgents = agents.filter(f => f.endsWith('.md'));
|
|
318
|
+
if (mdAgents.length > 0) {
|
|
319
|
+
for (const agent of mdAgents) {
|
|
320
|
+
logger.log(` • ${agent.replace('.md', '')}`);
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
logger.info(' No agents installed.');
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
logger.section('Commands');
|
|
328
|
+
if (await fs.pathExists(commandsDir)) {
|
|
329
|
+
const commands = await fs.readdir(commandsDir);
|
|
330
|
+
const mdCommands = commands.filter(f => f.endsWith('.md'));
|
|
331
|
+
if (mdCommands.length > 0) {
|
|
332
|
+
for (const command of mdCommands) {
|
|
333
|
+
logger.log(` • ${command.replace('.md', '')}`);
|
|
334
|
+
}
|
|
335
|
+
} else {
|
|
336
|
+
logger.info(' No commands installed.');
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Parse command line arguments
|
|
344
|
+
* @param {string[]} args - Command line arguments
|
|
345
|
+
* @returns {Object} Parsed options
|
|
346
|
+
*/
|
|
347
|
+
function parseArgs(args) {
|
|
348
|
+
const options = {
|
|
349
|
+
workflows: [],
|
|
350
|
+
cliType: 'claude',
|
|
351
|
+
interactive: false,
|
|
352
|
+
force: false,
|
|
353
|
+
verbose: false,
|
|
354
|
+
uninstall: false,
|
|
355
|
+
list: false,
|
|
356
|
+
help: false
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
let i = 0;
|
|
360
|
+
while (i < args.length) {
|
|
361
|
+
const arg = args[i];
|
|
362
|
+
|
|
363
|
+
if (arg === '--help' || arg === '-h') {
|
|
364
|
+
options.help = true;
|
|
365
|
+
} else if (arg === '--interactive' || arg === '-i') {
|
|
366
|
+
options.interactive = true;
|
|
367
|
+
} else if (arg === '--force' || arg === '-f') {
|
|
368
|
+
options.force = true;
|
|
369
|
+
} else if (arg === '--verbose' || arg === '-v') {
|
|
370
|
+
options.verbose = true;
|
|
371
|
+
} else if (arg === '--claude') {
|
|
372
|
+
options.cliType = 'claude';
|
|
373
|
+
} else if (arg === '--gemini') {
|
|
374
|
+
options.cliType = 'gemini';
|
|
375
|
+
} else if (arg === '--codex') {
|
|
376
|
+
options.cliType = 'codex';
|
|
377
|
+
} else if (arg === '--uninstall' || arg === '-u') {
|
|
378
|
+
options.uninstall = true;
|
|
379
|
+
} else if (arg === '--list' || arg === '-l') {
|
|
380
|
+
options.list = true;
|
|
381
|
+
} else if (!arg.startsWith('-')) {
|
|
382
|
+
options.workflows.push(arg);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
i++;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return options;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Show help message
|
|
393
|
+
*/
|
|
394
|
+
function showHelp() {
|
|
395
|
+
console.log(`
|
|
396
|
+
MyAIDev Method - Installation Script
|
|
397
|
+
|
|
398
|
+
Usage: node install.js [workflows...] [options]
|
|
399
|
+
|
|
400
|
+
Workflows:
|
|
401
|
+
core Core workflow with configuration tools
|
|
402
|
+
content Content creation with SEO optimization
|
|
403
|
+
visual Visual content generation
|
|
404
|
+
publish-wordpress WordPress publishing
|
|
405
|
+
development Development tools
|
|
406
|
+
security-pentest Security penetration testing
|
|
407
|
+
security-audit Security auditing
|
|
408
|
+
|
|
409
|
+
Options:
|
|
410
|
+
-h, --help Show this help message
|
|
411
|
+
-i, --interactive Run interactive workflow selection
|
|
412
|
+
-f, --force Force overwrite existing files
|
|
413
|
+
-v, --verbose Show detailed output
|
|
414
|
+
--claude Configure for Claude Code (default)
|
|
415
|
+
--gemini Configure for Gemini CLI
|
|
416
|
+
--codex Configure for Codex CLI
|
|
417
|
+
-l, --list List installed workflows
|
|
418
|
+
-u, --uninstall Uninstall specified workflows
|
|
419
|
+
|
|
420
|
+
Examples:
|
|
421
|
+
node install.js core content --claude
|
|
422
|
+
node install.js --interactive
|
|
423
|
+
node install.js --list
|
|
424
|
+
node install.js content --uninstall
|
|
425
|
+
`);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Main function
|
|
430
|
+
*/
|
|
431
|
+
async function main() {
|
|
432
|
+
const args = process.argv.slice(2);
|
|
433
|
+
const options = parseArgs(args);
|
|
434
|
+
|
|
435
|
+
if (options.help) {
|
|
436
|
+
showHelp();
|
|
437
|
+
process.exit(0);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const orchestrator = new InstallationOrchestrator();
|
|
441
|
+
|
|
442
|
+
try {
|
|
443
|
+
if (options.list) {
|
|
444
|
+
await orchestrator.listInstalled();
|
|
445
|
+
} else if (options.uninstall) {
|
|
446
|
+
if (options.workflows.length === 0) {
|
|
447
|
+
logger.error('Please specify workflows to uninstall.');
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
await orchestrator.uninstall(options.workflows);
|
|
451
|
+
} else {
|
|
452
|
+
await orchestrator.run(options);
|
|
453
|
+
}
|
|
454
|
+
} catch (error) {
|
|
455
|
+
logger.error(`Error: ${error.message}`);
|
|
456
|
+
process.exit(1);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
main();
|
|
@@ -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
|
+
}
|