@agents-at-scale/ark 0.1.31
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/README.md +95 -0
- package/dist/commands/cluster/get-ip.d.ts +2 -0
- package/dist/commands/cluster/get-ip.js +32 -0
- package/dist/commands/cluster/get-type.d.ts +2 -0
- package/dist/commands/cluster/get-type.js +26 -0
- package/dist/commands/cluster/index.d.ts +2 -0
- package/dist/commands/cluster/index.js +10 -0
- package/dist/commands/completion.d.ts +2 -0
- package/dist/commands/completion.js +108 -0
- package/dist/commands/config.d.ts +5 -0
- package/dist/commands/config.js +327 -0
- package/dist/commands/generate/config.d.ts +145 -0
- package/dist/commands/generate/config.js +253 -0
- package/dist/commands/generate/generators/agent.d.ts +2 -0
- package/dist/commands/generate/generators/agent.js +156 -0
- package/dist/commands/generate/generators/index.d.ts +6 -0
- package/dist/commands/generate/generators/index.js +6 -0
- package/dist/commands/generate/generators/marketplace.d.ts +2 -0
- package/dist/commands/generate/generators/marketplace.js +304 -0
- package/dist/commands/generate/generators/mcpserver.d.ts +25 -0
- package/dist/commands/generate/generators/mcpserver.js +350 -0
- package/dist/commands/generate/generators/project.d.ts +2 -0
- package/dist/commands/generate/generators/project.js +784 -0
- package/dist/commands/generate/generators/query.d.ts +2 -0
- package/dist/commands/generate/generators/query.js +213 -0
- package/dist/commands/generate/generators/team.d.ts +2 -0
- package/dist/commands/generate/generators/team.js +407 -0
- package/dist/commands/generate/index.d.ts +24 -0
- package/dist/commands/generate/index.js +357 -0
- package/dist/commands/generate/templateDiscovery.d.ts +30 -0
- package/dist/commands/generate/templateDiscovery.js +94 -0
- package/dist/commands/generate/templateEngine.d.ts +78 -0
- package/dist/commands/generate/templateEngine.js +368 -0
- package/dist/commands/generate/utils/nameUtils.d.ts +35 -0
- package/dist/commands/generate/utils/nameUtils.js +110 -0
- package/dist/commands/generate/utils/projectUtils.d.ts +28 -0
- package/dist/commands/generate/utils/projectUtils.js +133 -0
- package/dist/components/DashboardCLI.d.ts +3 -0
- package/dist/components/DashboardCLI.js +149 -0
- package/dist/components/GeneratorUI.d.ts +3 -0
- package/dist/components/GeneratorUI.js +167 -0
- package/dist/components/statusChecker.d.ts +48 -0
- package/dist/components/statusChecker.js +251 -0
- package/dist/config.d.ts +42 -0
- package/dist/config.js +243 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +67 -0
- package/dist/lib/arkClient.d.ts +32 -0
- package/dist/lib/arkClient.js +43 -0
- package/dist/lib/cluster.d.ts +8 -0
- package/dist/lib/cluster.js +134 -0
- package/dist/lib/config.d.ts +82 -0
- package/dist/lib/config.js +223 -0
- package/dist/lib/consts.d.ts +10 -0
- package/dist/lib/consts.js +15 -0
- package/dist/lib/errors.d.ts +56 -0
- package/dist/lib/errors.js +208 -0
- package/dist/lib/exec.d.ts +5 -0
- package/dist/lib/exec.js +20 -0
- package/dist/lib/gatewayManager.d.ts +24 -0
- package/dist/lib/gatewayManager.js +85 -0
- package/dist/lib/kubernetes.d.ts +28 -0
- package/dist/lib/kubernetes.js +122 -0
- package/dist/lib/progress.d.ts +128 -0
- package/dist/lib/progress.js +273 -0
- package/dist/lib/security.d.ts +37 -0
- package/dist/lib/security.js +295 -0
- package/dist/lib/types.d.ts +37 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/wrappers/git.d.ts +2 -0
- package/dist/lib/wrappers/git.js +43 -0
- package/dist/ui/MainMenu.d.ts +3 -0
- package/dist/ui/MainMenu.js +116 -0
- package/dist/ui/statusFormatter.d.ts +9 -0
- package/dist/ui/statusFormatter.js +47 -0
- package/package.json +62 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { TemplateDiscovery } from '../templateDiscovery.js';
|
|
6
|
+
import { TemplateEngine } from '../templateEngine.js';
|
|
7
|
+
import { ArkError, ErrorCode } from '../../../lib/errors.js';
|
|
8
|
+
export function createMarketplaceGenerator() {
|
|
9
|
+
const generator = new MarketplaceGenerator();
|
|
10
|
+
return {
|
|
11
|
+
name: 'marketplace',
|
|
12
|
+
description: 'Central repository for sharing reusable ARK components',
|
|
13
|
+
templatePath: 'marketplace',
|
|
14
|
+
generate: async (name, destination, options) => {
|
|
15
|
+
await generator.generate(name, destination, options);
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
class MarketplaceGenerator {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.templateDiscovery = new TemplateDiscovery();
|
|
22
|
+
this.templateEngine = new TemplateEngine();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get marketplace configuration from user input and validation
|
|
26
|
+
*/
|
|
27
|
+
async getMarketplaceConfig(destination, options) {
|
|
28
|
+
// Always use "ark-marketplace" as the name
|
|
29
|
+
const normalizedName = 'ark-marketplace';
|
|
30
|
+
const targetDir = path.resolve(destination, normalizedName);
|
|
31
|
+
// Check if directory already exists
|
|
32
|
+
if (fs.existsSync(targetDir)) {
|
|
33
|
+
throw new ArkError(`Directory ${targetDir} already exists. Please choose a different name or location.`, ErrorCode.VALIDATION_ERROR);
|
|
34
|
+
}
|
|
35
|
+
const config = {
|
|
36
|
+
name: normalizedName,
|
|
37
|
+
destination: targetDir,
|
|
38
|
+
description: 'Ark marketplace for sharing ARK components',
|
|
39
|
+
initGit: true,
|
|
40
|
+
};
|
|
41
|
+
// Get current git configuration
|
|
42
|
+
const gitConfig = await this.getGitUserConfig();
|
|
43
|
+
// Use command line options if provided, otherwise use current git config, fallback to defaults
|
|
44
|
+
config.initGit = !options.skipGit;
|
|
45
|
+
config.gitUserName =
|
|
46
|
+
options.gitUserName || gitConfig.name || 'Marketplace Team';
|
|
47
|
+
config.gitUserEmail =
|
|
48
|
+
options.gitUserEmail || gitConfig.email || 'marketplace@example.com';
|
|
49
|
+
return config;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Get current git user configuration (secure implementation)
|
|
53
|
+
*/
|
|
54
|
+
async getGitUserConfig() {
|
|
55
|
+
try {
|
|
56
|
+
const { execa } = await import('execa');
|
|
57
|
+
// Use secure PATH and argument arrays to prevent injection
|
|
58
|
+
const gitPath = process.env.GIT_PATH || '/usr/bin/git';
|
|
59
|
+
const secureEnv = {
|
|
60
|
+
PATH: '/usr/local/bin:/usr/bin:/bin',
|
|
61
|
+
...process.env,
|
|
62
|
+
};
|
|
63
|
+
const nameResult = await execa(gitPath, ['config', 'user.name'], {
|
|
64
|
+
env: secureEnv,
|
|
65
|
+
stdio: 'pipe',
|
|
66
|
+
timeout: 5000, // 5 second timeout
|
|
67
|
+
});
|
|
68
|
+
const emailResult = await execa(gitPath, ['config', 'user.email'], {
|
|
69
|
+
env: secureEnv,
|
|
70
|
+
stdio: 'pipe',
|
|
71
|
+
timeout: 5000,
|
|
72
|
+
});
|
|
73
|
+
return {
|
|
74
|
+
name: nameResult.stdout.trim(),
|
|
75
|
+
email: emailResult.stdout.trim(),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return {};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Get marketplace configuration with interactive prompts
|
|
84
|
+
*/
|
|
85
|
+
async getInteractiveConfig(config) {
|
|
86
|
+
console.log(chalk.cyan('\nšŖ Marketplace Configuration\n'));
|
|
87
|
+
const answers = await inquirer.prompt([
|
|
88
|
+
{
|
|
89
|
+
type: 'input',
|
|
90
|
+
name: 'destination',
|
|
91
|
+
message: 'Where would you like to create the marketplace?',
|
|
92
|
+
default: path.dirname(config.destination),
|
|
93
|
+
validate: (input) => {
|
|
94
|
+
if (!input.trim()) {
|
|
95
|
+
return 'Destination path is required';
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
type: 'confirm',
|
|
102
|
+
name: 'initGit',
|
|
103
|
+
message: `Initialize Git repository with ${config.gitUserName} <${config.gitUserEmail}>?`,
|
|
104
|
+
default: config.initGit,
|
|
105
|
+
},
|
|
106
|
+
]);
|
|
107
|
+
// Update config with answers
|
|
108
|
+
const updatedConfig = {
|
|
109
|
+
...config,
|
|
110
|
+
destination: path.resolve(answers.destination, config.name),
|
|
111
|
+
initGit: answers.initGit,
|
|
112
|
+
};
|
|
113
|
+
// Check if the updated destination already exists
|
|
114
|
+
if (fs.existsSync(updatedConfig.destination)) {
|
|
115
|
+
throw new ArkError(`Directory ${updatedConfig.destination} already exists. Please choose a different location.`, ErrorCode.VALIDATION_ERROR);
|
|
116
|
+
}
|
|
117
|
+
return updatedConfig;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Show configuration summary and ask for confirmation
|
|
121
|
+
*/
|
|
122
|
+
async confirmGeneration(config) {
|
|
123
|
+
console.log(chalk.cyan('\nš Marketplace Generation Summary\n'));
|
|
124
|
+
console.log(`Name: ${chalk.green(config.name)}`);
|
|
125
|
+
console.log(`Description: ${config.description}`);
|
|
126
|
+
console.log(`Destination: ${chalk.yellow(config.destination)}`);
|
|
127
|
+
console.log(`Git Repository: ${config.initGit ? chalk.green('Yes') : 'No'}`);
|
|
128
|
+
if (config.initGit) {
|
|
129
|
+
console.log(`Git User: ${config.gitUserName} <${config.gitUserEmail}>`);
|
|
130
|
+
}
|
|
131
|
+
const confirm = await inquirer.prompt([
|
|
132
|
+
{
|
|
133
|
+
type: 'confirm',
|
|
134
|
+
name: 'proceed',
|
|
135
|
+
message: 'Create marketplace with this configuration?',
|
|
136
|
+
default: true,
|
|
137
|
+
},
|
|
138
|
+
]);
|
|
139
|
+
if (!confirm.proceed) {
|
|
140
|
+
console.log(chalk.yellow('Marketplace generation cancelled.'));
|
|
141
|
+
process.exit(0);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Validate git configuration values to prevent injection
|
|
146
|
+
*/
|
|
147
|
+
validateGitConfig(value, type) {
|
|
148
|
+
if (!value || typeof value !== 'string') {
|
|
149
|
+
throw new ArkError(`Invalid git ${type}: must be a non-empty string`, ErrorCode.VALIDATION_ERROR);
|
|
150
|
+
}
|
|
151
|
+
// Prevent command injection by checking for dangerous characters
|
|
152
|
+
const dangerousChars = /[\\`$;|&<>(){}[\]]/;
|
|
153
|
+
if (dangerousChars.test(value)) {
|
|
154
|
+
throw new ArkError(`Invalid git ${type}: contains dangerous characters`, ErrorCode.VALIDATION_ERROR, undefined, ['Remove special characters and shell metacharacters']);
|
|
155
|
+
}
|
|
156
|
+
// Additional validation for email format
|
|
157
|
+
if (type === 'email') {
|
|
158
|
+
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
|
159
|
+
if (!emailRegex.test(value)) {
|
|
160
|
+
throw new ArkError(`Invalid git email format: ${value}`, ErrorCode.VALIDATION_ERROR);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Length validation
|
|
164
|
+
if (value.length > 100) {
|
|
165
|
+
throw new ArkError(`Git ${type} too long (max 100 characters)`, ErrorCode.VALIDATION_ERROR);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Setup git repository with initial commit (secure implementation)
|
|
170
|
+
*/
|
|
171
|
+
async setupGit(config) {
|
|
172
|
+
try {
|
|
173
|
+
const { execa } = await import('execa');
|
|
174
|
+
// Use secure PATH and git path
|
|
175
|
+
const gitPath = process.env.GIT_PATH || '/usr/bin/git';
|
|
176
|
+
const secureEnv = {
|
|
177
|
+
PATH: '/usr/local/bin:/usr/bin:/bin',
|
|
178
|
+
...process.env,
|
|
179
|
+
};
|
|
180
|
+
const execOptions = {
|
|
181
|
+
cwd: config.destination,
|
|
182
|
+
env: secureEnv,
|
|
183
|
+
stdio: 'pipe',
|
|
184
|
+
timeout: 10000, // 10 second timeout
|
|
185
|
+
};
|
|
186
|
+
// Initialize git repository
|
|
187
|
+
await execa(gitPath, ['init'], execOptions);
|
|
188
|
+
// Configure git user if provided (with validation)
|
|
189
|
+
if (config.gitUserName) {
|
|
190
|
+
this.validateGitConfig(config.gitUserName, 'name');
|
|
191
|
+
await execa(gitPath, ['config', 'user.name', config.gitUserName], execOptions);
|
|
192
|
+
}
|
|
193
|
+
if (config.gitUserEmail) {
|
|
194
|
+
this.validateGitConfig(config.gitUserEmail, 'email');
|
|
195
|
+
await execa(gitPath, ['config', 'user.email', config.gitUserEmail], execOptions);
|
|
196
|
+
}
|
|
197
|
+
// Add all files and create initial commit
|
|
198
|
+
await execa(gitPath, ['add', '.'], execOptions);
|
|
199
|
+
await execa(gitPath, ['commit', '-m', 'Initial marketplace structure'], execOptions);
|
|
200
|
+
console.log(chalk.green('ā
Git repository initialized'));
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
console.warn(chalk.yellow('ā ļø Git initialization failed:'), error instanceof Error ? error.message : String(error));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Generate marketplace structure
|
|
208
|
+
*/
|
|
209
|
+
async generateMarketplace(config) {
|
|
210
|
+
console.log(chalk.cyan('\nšļø Creating marketplace structure...'));
|
|
211
|
+
// Set template variables
|
|
212
|
+
const variables = {
|
|
213
|
+
projectName: config.name,
|
|
214
|
+
description: config.description,
|
|
215
|
+
PROJECT_NAME: config.name,
|
|
216
|
+
authorName: config.gitUserName || 'Marketplace Team',
|
|
217
|
+
authorEmail: config.gitUserEmail || 'marketplace@example.com',
|
|
218
|
+
};
|
|
219
|
+
this.templateEngine.setVariables(variables);
|
|
220
|
+
// Get template path
|
|
221
|
+
const templatePath = this.templateDiscovery.getTemplatePath('marketplace');
|
|
222
|
+
// Verify template exists
|
|
223
|
+
if (!this.templateDiscovery.templateExists('marketplace')) {
|
|
224
|
+
throw new ArkError('Marketplace template not found. Please ensure the template is available in the templates directory.', ErrorCode.TEMPLATE_ERROR);
|
|
225
|
+
}
|
|
226
|
+
// Configure exclude patterns
|
|
227
|
+
const excludePatterns = ['.git', 'node_modules', '.DS_Store'];
|
|
228
|
+
// Process template
|
|
229
|
+
await this.templateEngine.processTemplate(templatePath, config.destination, {
|
|
230
|
+
createDirectories: true,
|
|
231
|
+
exclude: excludePatterns,
|
|
232
|
+
});
|
|
233
|
+
console.log(chalk.green('ā
Marketplace structure created'));
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Show completion message with next steps
|
|
237
|
+
*/
|
|
238
|
+
showCompletionMessage(config) {
|
|
239
|
+
const relativePath = path.relative(process.cwd(), config.destination);
|
|
240
|
+
const displayPath = relativePath || config.destination;
|
|
241
|
+
console.log(chalk.green('\nš Marketplace created successfully!'));
|
|
242
|
+
console.log(chalk.cyan('\nš Location:'), displayPath);
|
|
243
|
+
console.log(chalk.cyan('š Description:'), config.description);
|
|
244
|
+
console.log(chalk.cyan('\nš Next steps:'));
|
|
245
|
+
console.log(' 1. ' + chalk.yellow('cd ' + displayPath));
|
|
246
|
+
console.log(' 2. ' + chalk.yellow('# Create remote repository on GitHub/GitLab'));
|
|
247
|
+
console.log(' 3. ' + chalk.yellow('git remote add origin <YOUR_REPO_URL>'));
|
|
248
|
+
console.log(' 4. ' + chalk.yellow('git push -u origin main'));
|
|
249
|
+
console.log(' 5. ' +
|
|
250
|
+
chalk.yellow('# Add your first component to the appropriate directory'));
|
|
251
|
+
console.log(' 6. ' +
|
|
252
|
+
chalk.yellow('# Update the README.md with marketplace-specific information'));
|
|
253
|
+
console.log(' 7. ' + chalk.yellow('# Set up CI/CD workflows in .github/workflows/'));
|
|
254
|
+
console.log(' 8. ' +
|
|
255
|
+
chalk.yellow('# Configure contribution guidelines and review process'));
|
|
256
|
+
console.log(chalk.cyan('\nš Directory structure:'));
|
|
257
|
+
console.log(' ⢠agents/ - Reusable agent definitions');
|
|
258
|
+
console.log(' ⢠teams/ - Multi-agent workflow configurations');
|
|
259
|
+
console.log(' ⢠models/ - Model configurations by provider');
|
|
260
|
+
console.log(' ⢠queries/ - Query templates and patterns');
|
|
261
|
+
console.log(' ⢠tools/ - Tool definitions and implementations');
|
|
262
|
+
console.log(' ⢠mcp-servers/ - MCP server configurations');
|
|
263
|
+
console.log(' ⢠projects/ - Complete Ark project templates and solutions');
|
|
264
|
+
console.log(' ⢠docs/ - Documentation and guides');
|
|
265
|
+
console.log(chalk.cyan('\nš” Tips:'));
|
|
266
|
+
console.log(' ⢠Each .keep file contains guidelines for that directory');
|
|
267
|
+
console.log(' ⢠Use the templates/ directory to create component templates');
|
|
268
|
+
console.log(' ⢠Set up automated validation in CI/CD pipelines');
|
|
269
|
+
console.log(' ⢠Encourage comprehensive documentation for all components');
|
|
270
|
+
if (config.initGit) {
|
|
271
|
+
console.log(chalk.cyan('\nš Git repository:'));
|
|
272
|
+
console.log(' ⢠Repository initialized with initial commit');
|
|
273
|
+
console.log(' ⢠Ready to add remote origin and push to GitHub/GitLab');
|
|
274
|
+
console.log(' ⢠Consider setting up branch protection rules');
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Main generation method
|
|
279
|
+
*/
|
|
280
|
+
async generate(_name, destination, options) {
|
|
281
|
+
try {
|
|
282
|
+
// Get initial configuration
|
|
283
|
+
let config = await this.getMarketplaceConfig(destination, options);
|
|
284
|
+
// Get interactive configuration (allows editing destination and git settings)
|
|
285
|
+
config = await this.getInteractiveConfig(config);
|
|
286
|
+
// Show summary and confirm
|
|
287
|
+
await this.confirmGeneration(config);
|
|
288
|
+
// Generate marketplace
|
|
289
|
+
await this.generateMarketplace(config);
|
|
290
|
+
// Setup git if requested
|
|
291
|
+
if (config.initGit) {
|
|
292
|
+
await this.setupGit(config);
|
|
293
|
+
}
|
|
294
|
+
// Show completion message
|
|
295
|
+
this.showCompletionMessage(config);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
if (error instanceof ArkError) {
|
|
299
|
+
throw error;
|
|
300
|
+
}
|
|
301
|
+
throw new ArkError(`Failed to generate marketplace: ${error instanceof Error ? error.message : String(error)}`, ErrorCode.UNKNOWN_ERROR);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface McpServerConfig {
|
|
2
|
+
mcpServerName: string;
|
|
3
|
+
description: string;
|
|
4
|
+
technology: 'node' | 'deno' | 'go' | 'python';
|
|
5
|
+
packageSource: 'local' | 'npm' | 'jsr' | 'go-install' | 'pip';
|
|
6
|
+
packageName?: string;
|
|
7
|
+
destination: string;
|
|
8
|
+
requiresAuth: boolean;
|
|
9
|
+
hasCustomConfig: boolean;
|
|
10
|
+
maintainerName: string;
|
|
11
|
+
homeUrl?: string;
|
|
12
|
+
tools: Array<{
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
}>;
|
|
16
|
+
packageManager: string;
|
|
17
|
+
sourceUrls: string[];
|
|
18
|
+
sampleQuery: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function createMcpServerGenerator(): {
|
|
21
|
+
name: string;
|
|
22
|
+
description: string;
|
|
23
|
+
templatePath: string;
|
|
24
|
+
generate: (name: string, destination: string, options: any) => Promise<void>;
|
|
25
|
+
};
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { TemplateEngine } from '../templateEngine.js';
|
|
6
|
+
import { TemplateDiscovery } from '../templateDiscovery.js';
|
|
7
|
+
export function createMcpServerGenerator() {
|
|
8
|
+
return {
|
|
9
|
+
name: 'mcp-server',
|
|
10
|
+
description: 'Generate a new MCP server with Kubernetes deployment from template',
|
|
11
|
+
templatePath: 'templates/mcp-server',
|
|
12
|
+
generate: async (name, destination, options) => {
|
|
13
|
+
const generator = new McpServerGenerator();
|
|
14
|
+
await generator.generate(name, destination, options);
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
class McpServerGenerator {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.templateDiscovery = new TemplateDiscovery();
|
|
21
|
+
this.templateEngine = new TemplateEngine();
|
|
22
|
+
}
|
|
23
|
+
async generate(name, destination, _options) {
|
|
24
|
+
console.log(chalk.blue('š ARK MCP Server Generator\n'));
|
|
25
|
+
// Get MCP server configuration
|
|
26
|
+
const config = await this.getMcpServerConfig(name, destination);
|
|
27
|
+
// Show summary and confirm
|
|
28
|
+
await this.showSummaryAndConfirm(config);
|
|
29
|
+
// Generate the MCP server
|
|
30
|
+
await this.generateMcpServer(config);
|
|
31
|
+
// Show next steps
|
|
32
|
+
this.showNextSteps(config);
|
|
33
|
+
}
|
|
34
|
+
async getMcpServerConfig(name, destination) {
|
|
35
|
+
console.log(chalk.cyan('š MCP Server Configuration\n'));
|
|
36
|
+
// Determine if we're in a project context
|
|
37
|
+
const isInProject = this.isInProjectContext(destination);
|
|
38
|
+
const defaultDestination = isInProject
|
|
39
|
+
? path.join(destination, 'mcp-servers', name)
|
|
40
|
+
: path.join(destination, name);
|
|
41
|
+
const answers = await inquirer.prompt([
|
|
42
|
+
{
|
|
43
|
+
type: 'input',
|
|
44
|
+
name: 'mcpServerName',
|
|
45
|
+
message: 'MCP server name:',
|
|
46
|
+
default: name,
|
|
47
|
+
validate: (input) => {
|
|
48
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(input)) {
|
|
49
|
+
return 'MCP server name can only contain letters, numbers, hyphens, and underscores';
|
|
50
|
+
}
|
|
51
|
+
return true;
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
type: 'input',
|
|
56
|
+
name: 'description',
|
|
57
|
+
message: 'MCP server description:',
|
|
58
|
+
default: `A custom MCP server named ${name} with Kubernetes deployment`,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
type: 'list',
|
|
62
|
+
name: 'technology',
|
|
63
|
+
message: 'Choose the technology stack:',
|
|
64
|
+
choices: [
|
|
65
|
+
{ name: 'Node.js (JavaScript/TypeScript)', value: 'node' },
|
|
66
|
+
{ name: 'Deno (TypeScript)', value: 'deno' },
|
|
67
|
+
{ name: 'Go', value: 'go' },
|
|
68
|
+
{ name: 'Python', value: 'python' },
|
|
69
|
+
],
|
|
70
|
+
default: 'node',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: 'list',
|
|
74
|
+
name: 'packageSource',
|
|
75
|
+
message: 'Package source:',
|
|
76
|
+
choices: (answers) => {
|
|
77
|
+
switch (answers.technology) {
|
|
78
|
+
case 'node':
|
|
79
|
+
return [
|
|
80
|
+
{
|
|
81
|
+
name: 'Local development (custom implementation)',
|
|
82
|
+
value: 'local',
|
|
83
|
+
},
|
|
84
|
+
{ name: 'NPM package', value: 'npm' },
|
|
85
|
+
];
|
|
86
|
+
case 'deno':
|
|
87
|
+
return [
|
|
88
|
+
{
|
|
89
|
+
name: 'Local development (custom implementation)',
|
|
90
|
+
value: 'local',
|
|
91
|
+
},
|
|
92
|
+
{ name: 'JSR package', value: 'jsr' },
|
|
93
|
+
];
|
|
94
|
+
case 'go':
|
|
95
|
+
return [
|
|
96
|
+
{
|
|
97
|
+
name: 'Local development (custom implementation)',
|
|
98
|
+
value: 'local',
|
|
99
|
+
},
|
|
100
|
+
{ name: 'Go install package', value: 'go-install' },
|
|
101
|
+
];
|
|
102
|
+
case 'python':
|
|
103
|
+
return [
|
|
104
|
+
{
|
|
105
|
+
name: 'Local development (custom implementation)',
|
|
106
|
+
value: 'local',
|
|
107
|
+
},
|
|
108
|
+
{ name: 'Pip package', value: 'pip' },
|
|
109
|
+
];
|
|
110
|
+
default:
|
|
111
|
+
return [{ name: 'Local development', value: 'local' }];
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
default: 'local',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
type: 'input',
|
|
118
|
+
name: 'packageName',
|
|
119
|
+
message: 'Package name (if using external package):',
|
|
120
|
+
when: (answers) => answers.packageSource !== 'local',
|
|
121
|
+
validate: (input, answers) => {
|
|
122
|
+
if (answers.packageSource !== 'local' && !input.trim()) {
|
|
123
|
+
return 'Package name is required when using external packages';
|
|
124
|
+
}
|
|
125
|
+
return true;
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
type: 'input',
|
|
130
|
+
name: 'destination',
|
|
131
|
+
message: 'Destination directory:',
|
|
132
|
+
default: defaultDestination,
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
type: 'confirm',
|
|
136
|
+
name: 'requiresAuth',
|
|
137
|
+
message: 'Does this MCP server require authentication?',
|
|
138
|
+
default: false,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
type: 'confirm',
|
|
142
|
+
name: 'hasCustomConfig',
|
|
143
|
+
message: 'Does this MCP server need custom configuration?',
|
|
144
|
+
default: false,
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
type: 'input',
|
|
148
|
+
name: 'maintainerName',
|
|
149
|
+
message: 'Maintainer name:',
|
|
150
|
+
default: 'QBAF Team',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
type: 'input',
|
|
154
|
+
name: 'homeUrl',
|
|
155
|
+
message: 'Home URL (optional):',
|
|
156
|
+
default: '',
|
|
157
|
+
},
|
|
158
|
+
]);
|
|
159
|
+
// Get tool definitions
|
|
160
|
+
const tools = await this.getToolDefinitions();
|
|
161
|
+
// Check if destination exists
|
|
162
|
+
if (fs.existsSync(answers.destination)) {
|
|
163
|
+
const overwrite = await inquirer.prompt([
|
|
164
|
+
{
|
|
165
|
+
type: 'confirm',
|
|
166
|
+
name: 'overwrite',
|
|
167
|
+
message: `Directory ${answers.destination} already exists. Overwrite?`,
|
|
168
|
+
default: false,
|
|
169
|
+
},
|
|
170
|
+
]);
|
|
171
|
+
if (!overwrite.overwrite) {
|
|
172
|
+
console.log(chalk.yellow('Operation cancelled.'));
|
|
173
|
+
process.exit(0);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
...answers,
|
|
178
|
+
tools,
|
|
179
|
+
packageManager: this.getPackageManager(answers.technology),
|
|
180
|
+
sourceUrls: answers.homeUrl ? [answers.homeUrl] : [],
|
|
181
|
+
sampleQuery: this.generateSampleQuery(tools),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
async getToolDefinitions() {
|
|
185
|
+
console.log(chalk.cyan('\nš§ Tool Definitions\n'));
|
|
186
|
+
console.log('Define the tools this MCP server will provide:');
|
|
187
|
+
const tools = [];
|
|
188
|
+
const addMore = await inquirer.prompt([
|
|
189
|
+
{
|
|
190
|
+
type: 'confirm',
|
|
191
|
+
name: 'addTools',
|
|
192
|
+
message: 'Add tool definitions?',
|
|
193
|
+
default: true,
|
|
194
|
+
},
|
|
195
|
+
]);
|
|
196
|
+
if (addMore.addTools) {
|
|
197
|
+
let addingTools = true;
|
|
198
|
+
while (addingTools) {
|
|
199
|
+
const toolInfo = await inquirer.prompt([
|
|
200
|
+
{
|
|
201
|
+
type: 'input',
|
|
202
|
+
name: 'name',
|
|
203
|
+
message: 'Tool name:',
|
|
204
|
+
validate: (input) => {
|
|
205
|
+
if (!input.trim()) {
|
|
206
|
+
return 'Tool name is required';
|
|
207
|
+
}
|
|
208
|
+
return true;
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
type: 'input',
|
|
213
|
+
name: 'description',
|
|
214
|
+
message: 'Tool description:',
|
|
215
|
+
validate: (input) => {
|
|
216
|
+
if (!input.trim()) {
|
|
217
|
+
return 'Tool description is required';
|
|
218
|
+
}
|
|
219
|
+
return true;
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
]);
|
|
223
|
+
tools.push(toolInfo);
|
|
224
|
+
const continueAdding = await inquirer.prompt([
|
|
225
|
+
{
|
|
226
|
+
type: 'confirm',
|
|
227
|
+
name: 'continue',
|
|
228
|
+
message: 'Add another tool?',
|
|
229
|
+
default: false,
|
|
230
|
+
},
|
|
231
|
+
]);
|
|
232
|
+
addingTools = continueAdding.continue;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Add default tool if none were added
|
|
236
|
+
if (tools.length === 0) {
|
|
237
|
+
tools.push({
|
|
238
|
+
name: 'example_tool',
|
|
239
|
+
description: 'An example tool provided by this MCP server',
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return tools;
|
|
243
|
+
}
|
|
244
|
+
getPackageManager(technology) {
|
|
245
|
+
switch (technology) {
|
|
246
|
+
case 'node':
|
|
247
|
+
return 'npm';
|
|
248
|
+
case 'deno':
|
|
249
|
+
return 'deno';
|
|
250
|
+
case 'python':
|
|
251
|
+
return 'pip';
|
|
252
|
+
case 'go':
|
|
253
|
+
return 'go';
|
|
254
|
+
default:
|
|
255
|
+
return 'npm';
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
generateSampleQuery(tools) {
|
|
259
|
+
if (tools.length === 0)
|
|
260
|
+
return 'Test the MCP server functionality.';
|
|
261
|
+
const toolNames = tools.map((t) => t.name).join(', ');
|
|
262
|
+
return `Test the MCP server by using the available tools: ${toolNames}. Please demonstrate how each tool works.`;
|
|
263
|
+
}
|
|
264
|
+
isInProjectContext(destination) {
|
|
265
|
+
// Check if we're in an ARK project by looking for common project files
|
|
266
|
+
const projectMarkers = [
|
|
267
|
+
'Makefile',
|
|
268
|
+
'Chart.yaml',
|
|
269
|
+
'agents/',
|
|
270
|
+
'models/',
|
|
271
|
+
'mcp-servers/',
|
|
272
|
+
];
|
|
273
|
+
return projectMarkers.some((marker) => fs.existsSync(path.join(destination, marker)));
|
|
274
|
+
}
|
|
275
|
+
async showSummaryAndConfirm(config) {
|
|
276
|
+
console.log(chalk.cyan('\nš Configuration Summary\n'));
|
|
277
|
+
console.log(`MCP Server Name: ${chalk.green(config.mcpServerName)}`);
|
|
278
|
+
console.log(`Description: ${config.description}`);
|
|
279
|
+
console.log(`Technology: ${chalk.blue(config.technology)}`);
|
|
280
|
+
console.log(`Package Source: ${config.packageSource}`);
|
|
281
|
+
if (config.packageName) {
|
|
282
|
+
console.log(`Package Name: ${config.packageName}`);
|
|
283
|
+
}
|
|
284
|
+
console.log(`Destination: ${config.destination}`);
|
|
285
|
+
console.log(`Authentication: ${config.requiresAuth ? chalk.red('Required') : chalk.green('Not required')}`);
|
|
286
|
+
console.log(`Custom Config: ${config.hasCustomConfig ? chalk.yellow('Yes') : 'No'}`);
|
|
287
|
+
console.log(`Tools: ${config.tools.map((t) => t.name).join(', ')}`);
|
|
288
|
+
const confirm = await inquirer.prompt([
|
|
289
|
+
{
|
|
290
|
+
type: 'confirm',
|
|
291
|
+
name: 'proceed',
|
|
292
|
+
message: 'Generate MCP server with this configuration?',
|
|
293
|
+
default: true,
|
|
294
|
+
},
|
|
295
|
+
]);
|
|
296
|
+
if (!confirm.proceed) {
|
|
297
|
+
console.log(chalk.yellow('Operation cancelled.'));
|
|
298
|
+
process.exit(0);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
async generateMcpServer(config) {
|
|
302
|
+
console.log(chalk.blue('\nš Generating MCP Server...\n'));
|
|
303
|
+
try {
|
|
304
|
+
const templatePath = this.templateDiscovery.getTemplatePath('mcp-server');
|
|
305
|
+
if (!templatePath) {
|
|
306
|
+
throw new Error('MCP server template not found');
|
|
307
|
+
}
|
|
308
|
+
// Generate from template
|
|
309
|
+
// Set template variables using the same structure expected by templates
|
|
310
|
+
this.templateEngine.setVariables(config);
|
|
311
|
+
await this.templateEngine.processTemplate(templatePath, config.destination);
|
|
312
|
+
// Make build script executable (owner only for security)
|
|
313
|
+
const buildScriptPath = path.join(config.destination, 'build.sh');
|
|
314
|
+
if (fs.existsSync(buildScriptPath)) {
|
|
315
|
+
fs.chmodSync(buildScriptPath, '700'); // rwx------ (owner only)
|
|
316
|
+
}
|
|
317
|
+
console.log(chalk.green(`ā
MCP server generated successfully at ${config.destination}`));
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
console.error(chalk.red('Failed to generate MCP server:'), error.message);
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
showNextSteps(config) {
|
|
325
|
+
console.log(chalk.cyan('\nšÆ Next Steps:\n'));
|
|
326
|
+
console.log(`1. Navigate to your MCP server directory:`);
|
|
327
|
+
console.log(chalk.yellow(` cd ${config.destination}`));
|
|
328
|
+
console.log(``);
|
|
329
|
+
console.log(`2. Build the Docker image:`);
|
|
330
|
+
console.log(chalk.yellow(` make build`));
|
|
331
|
+
console.log(``);
|
|
332
|
+
console.log(`3. Install to Kubernetes:`);
|
|
333
|
+
if (config.requiresAuth) {
|
|
334
|
+
console.log(chalk.yellow(` make install AUTH_TOKEN=your-auth-token`));
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
console.log(chalk.yellow(` make install`));
|
|
338
|
+
}
|
|
339
|
+
console.log(``);
|
|
340
|
+
console.log(`4. Check deployment status:`);
|
|
341
|
+
console.log(chalk.yellow(` make status`));
|
|
342
|
+
console.log(``);
|
|
343
|
+
console.log(`5. Deploy example agent and query:`);
|
|
344
|
+
console.log(chalk.yellow(` make deploy-examples`));
|
|
345
|
+
console.log(``);
|
|
346
|
+
console.log(chalk.green('š Your MCP server is ready to use!'));
|
|
347
|
+
console.log(``);
|
|
348
|
+
console.log('For more information, see the README.md file in your server directory.');
|
|
349
|
+
}
|
|
350
|
+
}
|