@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,213 @@
|
|
|
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
|
+
import { toKebabCase, validateNameStrict } from '../utils/nameUtils.js';
|
|
8
|
+
import { getCurrentProjectInfo } from '../utils/projectUtils.js';
|
|
9
|
+
import { ErrorHandler, TemplateError, ValidationError, } from '../../../lib/errors.js';
|
|
10
|
+
export function createQueryGenerator() {
|
|
11
|
+
return {
|
|
12
|
+
name: 'query',
|
|
13
|
+
description: 'Generate a new query to test agents or teams',
|
|
14
|
+
templatePath: 'templates/query',
|
|
15
|
+
generate: async (name, destination, options) => {
|
|
16
|
+
const generator = new QueryGenerator();
|
|
17
|
+
await generator.generate(name, destination, options);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
class QueryGenerator {
|
|
22
|
+
constructor() {
|
|
23
|
+
this.templateDiscovery = new TemplateDiscovery();
|
|
24
|
+
this.templateEngine = new TemplateEngine();
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get query configuration from user input and validation
|
|
28
|
+
*/
|
|
29
|
+
async getQueryConfig(name, _destination, _options) {
|
|
30
|
+
// Validate that we're in a project directory and get project info
|
|
31
|
+
const { projectName, projectDir } = getCurrentProjectInfo();
|
|
32
|
+
// Validate and normalize query name
|
|
33
|
+
const queryName = toKebabCase(name);
|
|
34
|
+
validateNameStrict(queryName, 'query name');
|
|
35
|
+
// Check if query already exists
|
|
36
|
+
const queriesDir = path.join(projectDir, 'queries');
|
|
37
|
+
const queryFilePath = path.join(queriesDir, `${queryName}-query.yaml`);
|
|
38
|
+
if (fs.existsSync(queryFilePath)) {
|
|
39
|
+
console.log(chalk.yellow(`ā ļø Query file already exists: ${queryFilePath}`));
|
|
40
|
+
const { overwrite } = await inquirer.prompt([
|
|
41
|
+
{
|
|
42
|
+
type: 'confirm',
|
|
43
|
+
name: 'overwrite',
|
|
44
|
+
message: 'Do you want to overwrite the existing query?',
|
|
45
|
+
default: false,
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
if (!overwrite) {
|
|
49
|
+
throw new ValidationError('Query generation cancelled by user', 'overwrite', [
|
|
50
|
+
`Use a different query name`,
|
|
51
|
+
`Use --force flag to overwrite without prompting`,
|
|
52
|
+
]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Ask user what type of target they want
|
|
56
|
+
const { targetType } = await inquirer.prompt([
|
|
57
|
+
{
|
|
58
|
+
type: 'list',
|
|
59
|
+
name: 'targetType',
|
|
60
|
+
message: 'What should this query target?',
|
|
61
|
+
choices: [
|
|
62
|
+
{ name: 'Agent - Target a single AI agent', value: 'agent' },
|
|
63
|
+
{ name: 'Team - Target a team of agents', value: 'team' },
|
|
64
|
+
],
|
|
65
|
+
default: 'agent',
|
|
66
|
+
},
|
|
67
|
+
]);
|
|
68
|
+
// Get available targets based on type
|
|
69
|
+
const targetDir = path.join(projectDir, targetType === 'agent' ? 'agents' : 'teams');
|
|
70
|
+
const filePattern = targetType === 'agent' ? '-agent.yaml' : '-team.yaml';
|
|
71
|
+
let availableTargets = [];
|
|
72
|
+
if (fs.existsSync(targetDir)) {
|
|
73
|
+
const targetFiles = fs
|
|
74
|
+
.readdirSync(targetDir)
|
|
75
|
+
.filter((file) => file.endsWith(filePattern))
|
|
76
|
+
.map((file) => file.replace(filePattern, ''));
|
|
77
|
+
availableTargets = targetFiles;
|
|
78
|
+
}
|
|
79
|
+
let targetName;
|
|
80
|
+
if (availableTargets.length > 0) {
|
|
81
|
+
// Ask user to select a target or provide a custom one
|
|
82
|
+
const choices = [
|
|
83
|
+
...availableTargets.map((target) => ({ name: target, value: target })),
|
|
84
|
+
{ name: 'Other (specify manually)', value: 'custom' },
|
|
85
|
+
];
|
|
86
|
+
const { selectedTarget } = await inquirer.prompt([
|
|
87
|
+
{
|
|
88
|
+
type: 'list',
|
|
89
|
+
name: 'selectedTarget',
|
|
90
|
+
message: `Which ${targetType} should this query target?`,
|
|
91
|
+
choices,
|
|
92
|
+
default: availableTargets[0],
|
|
93
|
+
},
|
|
94
|
+
]);
|
|
95
|
+
if (selectedTarget === 'custom') {
|
|
96
|
+
const { customTarget } = await inquirer.prompt([
|
|
97
|
+
{
|
|
98
|
+
type: 'input',
|
|
99
|
+
name: 'customTarget',
|
|
100
|
+
message: `Enter the ${targetType} name:`,
|
|
101
|
+
validate: (input) => {
|
|
102
|
+
if (!input.trim()) {
|
|
103
|
+
return `${targetType} name cannot be empty`;
|
|
104
|
+
}
|
|
105
|
+
return true;
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
]);
|
|
109
|
+
targetName = toKebabCase(customTarget);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
targetName = selectedTarget;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
// No targets found, ask for manual input
|
|
117
|
+
console.log(chalk.yellow(`ā ļø No ${targetType}s found in the project.`));
|
|
118
|
+
const { customTarget } = await inquirer.prompt([
|
|
119
|
+
{
|
|
120
|
+
type: 'input',
|
|
121
|
+
name: 'customTarget',
|
|
122
|
+
message: `Enter the ${targetType} name this query should target:`,
|
|
123
|
+
validate: (input) => {
|
|
124
|
+
if (!input.trim()) {
|
|
125
|
+
return `${targetType} name cannot be empty`;
|
|
126
|
+
}
|
|
127
|
+
return true;
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
]);
|
|
131
|
+
targetName = toKebabCase(customTarget);
|
|
132
|
+
}
|
|
133
|
+
// Ask for the input message
|
|
134
|
+
const { inputMessage } = await inquirer.prompt([
|
|
135
|
+
{
|
|
136
|
+
type: 'input',
|
|
137
|
+
name: 'inputMessage',
|
|
138
|
+
message: 'What message should the query send to the agent?',
|
|
139
|
+
default: `Hello! Can you help me understand what you can do for the ${projectName} project?`,
|
|
140
|
+
validate: (input) => {
|
|
141
|
+
if (!input.trim()) {
|
|
142
|
+
return 'Input message cannot be empty';
|
|
143
|
+
}
|
|
144
|
+
return true;
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
]);
|
|
148
|
+
return {
|
|
149
|
+
name,
|
|
150
|
+
queryName,
|
|
151
|
+
projectName,
|
|
152
|
+
projectDirectory: projectDir,
|
|
153
|
+
targetType,
|
|
154
|
+
targetName,
|
|
155
|
+
inputMessage,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Generate the query file
|
|
160
|
+
*/
|
|
161
|
+
async generateQueryFile(config) {
|
|
162
|
+
const templatePath = this.templateDiscovery.getTemplatePath('query');
|
|
163
|
+
if (!this.templateDiscovery.templateExists('query')) {
|
|
164
|
+
throw new TemplateError(`Query template not found at: ${templatePath}`, templatePath, [
|
|
165
|
+
'Ensure the templates directory exists',
|
|
166
|
+
'Check that the query template is properly installed',
|
|
167
|
+
'Verify file permissions',
|
|
168
|
+
]);
|
|
169
|
+
}
|
|
170
|
+
// Set up template variables
|
|
171
|
+
const variables = {
|
|
172
|
+
queryName: config.queryName,
|
|
173
|
+
targetType: config.targetType,
|
|
174
|
+
targetName: config.targetName,
|
|
175
|
+
projectName: config.projectName,
|
|
176
|
+
inputMessage: config.inputMessage,
|
|
177
|
+
};
|
|
178
|
+
this.templateEngine.setVariables(variables);
|
|
179
|
+
// Ensure queries directory exists
|
|
180
|
+
const queriesDir = path.join(config.projectDirectory, 'queries');
|
|
181
|
+
if (!fs.existsSync(queriesDir)) {
|
|
182
|
+
fs.mkdirSync(queriesDir, { recursive: true });
|
|
183
|
+
console.log(chalk.blue(`š Created queries directory: ${queriesDir}`));
|
|
184
|
+
}
|
|
185
|
+
// Process the query template file
|
|
186
|
+
const templateFilePath = path.join(templatePath, 'query.template.yaml');
|
|
187
|
+
const destinationFilePath = path.join(queriesDir, `${config.queryName}-query.yaml`);
|
|
188
|
+
await this.templateEngine.processFile(templateFilePath, destinationFilePath);
|
|
189
|
+
console.log(chalk.green(`ā
Query file created: queries/${config.queryName}-query.yaml`));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Main generation method
|
|
193
|
+
*/
|
|
194
|
+
async generate(name, destination, options) {
|
|
195
|
+
await ErrorHandler.catchAndHandle(async () => {
|
|
196
|
+
console.log(chalk.blue('š Generating query...'));
|
|
197
|
+
// Get configuration
|
|
198
|
+
const config = await this.getQueryConfig(name, destination, options);
|
|
199
|
+
console.log(chalk.blue(`\nš Query details:`));
|
|
200
|
+
console.log(chalk.gray(` Name: ${config.queryName}`));
|
|
201
|
+
console.log(chalk.gray(` Target ${config.targetType}: ${config.targetName}`));
|
|
202
|
+
console.log(chalk.gray(` Project: ${config.projectName}`));
|
|
203
|
+
console.log(chalk.gray(` Message: ${config.inputMessage.substring(0, 50)}${config.inputMessage.length > 50 ? '...' : ''}`));
|
|
204
|
+
// Generate query file
|
|
205
|
+
await this.generateQueryFile(config);
|
|
206
|
+
console.log(chalk.green('\nš Query generation completed successfully!'));
|
|
207
|
+
console.log(chalk.cyan('\nš” Next steps:'));
|
|
208
|
+
console.log(chalk.gray(` 1. Review the generated query: queries/${config.queryName}-query.yaml`));
|
|
209
|
+
console.log(chalk.gray(` 2. Test the query: kubectl apply -f queries/${config.queryName}-query.yaml`));
|
|
210
|
+
console.log(chalk.gray(` 3. Check results: kubectl get queries`));
|
|
211
|
+
}, 'Generating query');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,407 @@
|
|
|
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
|
+
import { toKebabCase, isValidKubernetesName } from '../utils/nameUtils.js';
|
|
8
|
+
import { validateCurrentProject, getCurrentProjectDirectory, } from '../utils/projectUtils.js';
|
|
9
|
+
export function createTeamGenerator() {
|
|
10
|
+
return {
|
|
11
|
+
name: 'team',
|
|
12
|
+
description: 'Generate a new team with selected agents',
|
|
13
|
+
templatePath: 'templates/team',
|
|
14
|
+
generate: async (name, destination, options) => {
|
|
15
|
+
const generator = new TeamGenerator();
|
|
16
|
+
await generator.generate(name, destination, options);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
class TeamGenerator {
|
|
21
|
+
constructor() {
|
|
22
|
+
this.templateDiscovery = new TemplateDiscovery();
|
|
23
|
+
this.templateEngine = new TemplateEngine();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Discover existing agents in the project
|
|
27
|
+
*/
|
|
28
|
+
async discoverAgents(projectDir) {
|
|
29
|
+
const agentsDir = path.join(projectDir, 'agents');
|
|
30
|
+
const agents = [];
|
|
31
|
+
if (!fs.existsSync(agentsDir)) {
|
|
32
|
+
return agents;
|
|
33
|
+
}
|
|
34
|
+
const files = fs.readdirSync(agentsDir);
|
|
35
|
+
for (const file of files) {
|
|
36
|
+
if (file.endsWith('-agent.yaml') && file !== '.keep') {
|
|
37
|
+
const filePath = path.join(agentsDir, file);
|
|
38
|
+
const agentName = file.replace('-agent.yaml', '');
|
|
39
|
+
agents.push({
|
|
40
|
+
name: agentName,
|
|
41
|
+
fileName: file,
|
|
42
|
+
path: filePath,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return agents;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get team configuration from user input and validation
|
|
50
|
+
*/
|
|
51
|
+
async getTeamConfig(name, _destination, _options) {
|
|
52
|
+
// Validate that we're in a project directory
|
|
53
|
+
const projectDir = getCurrentProjectDirectory();
|
|
54
|
+
const validation = validateCurrentProject();
|
|
55
|
+
if (!validation.isValid) {
|
|
56
|
+
throw new Error(validation.error || 'Invalid project structure');
|
|
57
|
+
}
|
|
58
|
+
const projectName = validation.projectName || path.basename(projectDir);
|
|
59
|
+
// Normalize team name
|
|
60
|
+
const teamName = toKebabCase(name);
|
|
61
|
+
if (!isValidKubernetesName(teamName)) {
|
|
62
|
+
throw new Error(`Invalid team name: ${teamName}. Must be lowercase kebab-case`);
|
|
63
|
+
}
|
|
64
|
+
// Check if team already exists
|
|
65
|
+
const teamsDir = path.join(projectDir, 'teams');
|
|
66
|
+
const teamFilePath = path.join(teamsDir, `${teamName}-team.yaml`);
|
|
67
|
+
if (fs.existsSync(teamFilePath)) {
|
|
68
|
+
console.log(chalk.yellow(`ā ļø Team file already exists: ${teamFilePath}`));
|
|
69
|
+
const { overwrite } = await inquirer.prompt([
|
|
70
|
+
{
|
|
71
|
+
type: 'confirm',
|
|
72
|
+
name: 'overwrite',
|
|
73
|
+
message: 'Do you want to overwrite the existing team?',
|
|
74
|
+
default: false,
|
|
75
|
+
},
|
|
76
|
+
]);
|
|
77
|
+
if (!overwrite) {
|
|
78
|
+
throw new Error('Team generation cancelled');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Discover existing agents
|
|
82
|
+
const existingAgents = await this.discoverAgents(projectDir);
|
|
83
|
+
// Select team strategy
|
|
84
|
+
const { strategy } = await inquirer.prompt([
|
|
85
|
+
{
|
|
86
|
+
type: 'list',
|
|
87
|
+
name: 'strategy',
|
|
88
|
+
message: 'Select team strategy:',
|
|
89
|
+
choices: [
|
|
90
|
+
{ name: 'Sequential - Agents execute in order', value: 'sequential' },
|
|
91
|
+
{ name: 'Round Robin - Agents take turns', value: 'round-robin' },
|
|
92
|
+
{ name: 'Graph - Custom workflow with dependencies', value: 'graph' },
|
|
93
|
+
{ name: 'Selector - AI chooses the next agent', value: 'selector' },
|
|
94
|
+
],
|
|
95
|
+
default: 'sequential',
|
|
96
|
+
},
|
|
97
|
+
]);
|
|
98
|
+
// Select agents for the team
|
|
99
|
+
const teamMembers = await this.selectTeamMembers(existingAgents, projectName, projectDir);
|
|
100
|
+
// Ask if user wants to create a query for the team
|
|
101
|
+
const { createQuery } = await inquirer.prompt([
|
|
102
|
+
{
|
|
103
|
+
type: 'confirm',
|
|
104
|
+
name: 'createQuery',
|
|
105
|
+
message: `Would you like to create a sample query for the ${teamName} team?`,
|
|
106
|
+
default: true,
|
|
107
|
+
},
|
|
108
|
+
]);
|
|
109
|
+
return {
|
|
110
|
+
name,
|
|
111
|
+
teamName,
|
|
112
|
+
projectName,
|
|
113
|
+
projectDirectory: projectDir,
|
|
114
|
+
strategy,
|
|
115
|
+
members: teamMembers,
|
|
116
|
+
createQuery,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Build choices for existing agents
|
|
121
|
+
*/
|
|
122
|
+
buildExistingAgentChoices(existingAgents, members) {
|
|
123
|
+
if (existingAgents.length === 0) {
|
|
124
|
+
return [];
|
|
125
|
+
}
|
|
126
|
+
const choices = [
|
|
127
|
+
{
|
|
128
|
+
name: '--- Select from existing agents ---',
|
|
129
|
+
value: 'separator',
|
|
130
|
+
disabled: true,
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
existingAgents.forEach((agent) => {
|
|
134
|
+
const alreadySelected = members.some((m) => m.name === `${agent.name}-agent`);
|
|
135
|
+
choices.push({
|
|
136
|
+
name: `${agent.name}-agent${alreadySelected ? ' (already selected)' : ''}`,
|
|
137
|
+
value: `existing:${agent.name}`,
|
|
138
|
+
disabled: alreadySelected,
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
return choices;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Build action choices for the member selection menu
|
|
145
|
+
*/
|
|
146
|
+
buildActionChoices(members) {
|
|
147
|
+
const choices = [
|
|
148
|
+
{ name: '--- Actions ---', value: 'separator', disabled: true },
|
|
149
|
+
{ name: 'ā Create a new agent', value: 'create-new' },
|
|
150
|
+
{ name: 'ā
Done selecting members', value: 'done' },
|
|
151
|
+
];
|
|
152
|
+
if (members.length > 0) {
|
|
153
|
+
choices.push({ name: 'ā Remove last member', value: 'remove-last' });
|
|
154
|
+
}
|
|
155
|
+
return choices;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Handle different member selection actions
|
|
159
|
+
*/
|
|
160
|
+
async handleMemberAction(action, members, existingAgents, projectName, projectDir) {
|
|
161
|
+
if (action === 'done') {
|
|
162
|
+
if (members.length === 0) {
|
|
163
|
+
console.log(chalk.yellow('ā ļø A team must have at least one member'));
|
|
164
|
+
return false; // Continue the loop
|
|
165
|
+
}
|
|
166
|
+
return true; // Exit the loop
|
|
167
|
+
}
|
|
168
|
+
if (action === 'remove-last') {
|
|
169
|
+
const removed = members.pop();
|
|
170
|
+
console.log(chalk.gray(`ā Removed: ${removed?.name}`));
|
|
171
|
+
return false; // Continue the loop
|
|
172
|
+
}
|
|
173
|
+
if (action === 'create-new') {
|
|
174
|
+
const newAgent = await this.createNewAgent(projectName, projectDir);
|
|
175
|
+
if (newAgent) {
|
|
176
|
+
members.push({ name: `${newAgent}-agent`, type: 'agent' });
|
|
177
|
+
console.log(chalk.green(`ā Added: ${newAgent}-agent`));
|
|
178
|
+
// Refresh existing agents list
|
|
179
|
+
const updatedAgents = await this.discoverAgents(projectDir);
|
|
180
|
+
existingAgents.length = 0;
|
|
181
|
+
existingAgents.push(...updatedAgents);
|
|
182
|
+
}
|
|
183
|
+
return false; // Continue the loop
|
|
184
|
+
}
|
|
185
|
+
if (action.startsWith('existing:')) {
|
|
186
|
+
const agentName = action.replace('existing:', '');
|
|
187
|
+
members.push({ name: `${agentName}-agent`, type: 'agent' });
|
|
188
|
+
console.log(chalk.green(`ā Added: ${agentName}-agent`));
|
|
189
|
+
return false; // Continue the loop
|
|
190
|
+
}
|
|
191
|
+
return false; // Continue the loop for unknown actions
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Interactive agent selection and creation
|
|
195
|
+
*/
|
|
196
|
+
async selectTeamMembers(existingAgents, projectName, projectDir) {
|
|
197
|
+
const members = [];
|
|
198
|
+
while (true) {
|
|
199
|
+
const existingChoices = this.buildExistingAgentChoices(existingAgents, members);
|
|
200
|
+
const actionChoices = this.buildActionChoices(members);
|
|
201
|
+
const choices = [...existingChoices, ...actionChoices];
|
|
202
|
+
const { action } = await inquirer.prompt([
|
|
203
|
+
{
|
|
204
|
+
type: 'list',
|
|
205
|
+
name: 'action',
|
|
206
|
+
message: `Select team members (${members.length} selected):`,
|
|
207
|
+
choices,
|
|
208
|
+
},
|
|
209
|
+
]);
|
|
210
|
+
const shouldExit = await this.handleMemberAction(action, members, existingAgents, projectName, projectDir);
|
|
211
|
+
if (shouldExit) {
|
|
212
|
+
break;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return members;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Create a new agent using the agent generator
|
|
219
|
+
*/
|
|
220
|
+
async createNewAgent(projectName, projectDir) {
|
|
221
|
+
const { agentName } = await inquirer.prompt([
|
|
222
|
+
{
|
|
223
|
+
type: 'input',
|
|
224
|
+
name: 'agentName',
|
|
225
|
+
message: 'Enter the name for the new agent:',
|
|
226
|
+
validate: (input) => {
|
|
227
|
+
if (!input.trim()) {
|
|
228
|
+
return 'Agent name cannot be empty';
|
|
229
|
+
}
|
|
230
|
+
const normalizedName = toKebabCase(input);
|
|
231
|
+
if (!isValidKubernetesName(normalizedName)) {
|
|
232
|
+
return 'Agent name must be lowercase kebab-case';
|
|
233
|
+
}
|
|
234
|
+
return true;
|
|
235
|
+
},
|
|
236
|
+
filter: (input) => toKebabCase(input.trim()),
|
|
237
|
+
},
|
|
238
|
+
]);
|
|
239
|
+
try {
|
|
240
|
+
// Use the template engine to create the agent
|
|
241
|
+
const agentTemplateEngine = new TemplateEngine();
|
|
242
|
+
const templatePath = this.templateDiscovery.getTemplatePath('agent');
|
|
243
|
+
if (!this.templateDiscovery.templateExists('agent')) {
|
|
244
|
+
throw new Error(`Agent template not found at: ${templatePath}`);
|
|
245
|
+
}
|
|
246
|
+
// Set up template variables
|
|
247
|
+
const variables = {
|
|
248
|
+
agentName,
|
|
249
|
+
projectName,
|
|
250
|
+
};
|
|
251
|
+
agentTemplateEngine.setVariables(variables);
|
|
252
|
+
// Process the agent template file
|
|
253
|
+
const templateFilePath = path.join(templatePath, 'agent.template.yaml');
|
|
254
|
+
const destinationFilePath = path.join(projectDir, 'agents', `${agentName}-agent.yaml`);
|
|
255
|
+
await agentTemplateEngine.processFile(templateFilePath, destinationFilePath);
|
|
256
|
+
console.log(chalk.green(`š Created agent: ${agentName}-agent`));
|
|
257
|
+
return agentName;
|
|
258
|
+
}
|
|
259
|
+
catch (error) {
|
|
260
|
+
console.error(chalk.red(`ā Failed to create agent: ${error}`));
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Generate the team file
|
|
266
|
+
*/
|
|
267
|
+
async generateTeamFile(config) {
|
|
268
|
+
const templatePath = this.templateDiscovery.getTemplatePath('team');
|
|
269
|
+
if (!this.templateDiscovery.templateExists('team')) {
|
|
270
|
+
throw new Error(`Team template not found at: ${templatePath}`);
|
|
271
|
+
}
|
|
272
|
+
// For simple teams, use the first agent as the primary agent
|
|
273
|
+
const primaryAgentName = config.members[0]?.name.replace('-agent', '') || 'default';
|
|
274
|
+
// Set up template variables
|
|
275
|
+
const variables = {
|
|
276
|
+
teamName: config.teamName,
|
|
277
|
+
projectName: config.projectName,
|
|
278
|
+
agentName: primaryAgentName,
|
|
279
|
+
};
|
|
280
|
+
this.templateEngine.setVariables(variables);
|
|
281
|
+
// Process the team template file
|
|
282
|
+
const templateFilePath = path.join(templatePath, 'team.template.yaml');
|
|
283
|
+
const destinationFilePath = path.join(config.projectDirectory, 'teams', `${config.teamName}-team.yaml`);
|
|
284
|
+
await this.templateEngine.processFile(templateFilePath, destinationFilePath);
|
|
285
|
+
// Post-process the file to update members and strategy
|
|
286
|
+
await this.updateTeamFile(destinationFilePath, config);
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Update the generated team file with selected members and strategy
|
|
290
|
+
*/
|
|
291
|
+
async updateTeamFile(filePath, config) {
|
|
292
|
+
let content = fs.readFileSync(filePath, 'utf-8');
|
|
293
|
+
// Update strategy
|
|
294
|
+
content = content.replace(/strategy: "sequential"/, `strategy: "${config.strategy}"`);
|
|
295
|
+
// Update members section using safe line-based replacement to avoid regex backtracking DoS
|
|
296
|
+
const lines = content.split('\n');
|
|
297
|
+
const membersHeaderIndex = lines.findIndex((line) => {
|
|
298
|
+
// Safe string-based check to avoid ReDoS vulnerability
|
|
299
|
+
const trimmed = line.trim();
|
|
300
|
+
return trimmed === 'members:';
|
|
301
|
+
});
|
|
302
|
+
if (membersHeaderIndex !== -1) {
|
|
303
|
+
const baseIndentRegex = /^\s*/;
|
|
304
|
+
const baseIndentMatch = baseIndentRegex.exec(lines[membersHeaderIndex]);
|
|
305
|
+
const baseIndent = baseIndentMatch ? baseIndentMatch[0] : '';
|
|
306
|
+
// Find the end of the current members block by scanning until a line with
|
|
307
|
+
// indentation less than or equal to the base indent (i.e., next top-level key)
|
|
308
|
+
let endIndex = membersHeaderIndex + 1;
|
|
309
|
+
while (endIndex < lines.length) {
|
|
310
|
+
const currentLine = lines[endIndex];
|
|
311
|
+
// Always advance through empty lines inside the block
|
|
312
|
+
if (currentLine.trim().length === 0) {
|
|
313
|
+
endIndex++;
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
const indentRegex = /^\s*/;
|
|
317
|
+
const currentIndent = (indentRegex.exec(currentLine) || [''])[0];
|
|
318
|
+
if (currentIndent.length <= baseIndent.length) {
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
endIndex++;
|
|
322
|
+
}
|
|
323
|
+
// Build the new members block with correct indentation derived from the file
|
|
324
|
+
const memberIndent = baseIndent + ' ';
|
|
325
|
+
const newMemberLines = [];
|
|
326
|
+
for (const member of config.members) {
|
|
327
|
+
newMemberLines.push(`${memberIndent}- name: ${member.name}`);
|
|
328
|
+
newMemberLines.push(`${memberIndent} type: ${member.type}`);
|
|
329
|
+
}
|
|
330
|
+
// Splice in the new block
|
|
331
|
+
const replacement = newMemberLines.length > 0 ? newMemberLines : [];
|
|
332
|
+
lines.splice(membersHeaderIndex + 1, Math.max(0, endIndex - (membersHeaderIndex + 1)), ...replacement);
|
|
333
|
+
content = lines.join('\n');
|
|
334
|
+
}
|
|
335
|
+
fs.writeFileSync(filePath, content);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Generate the query file for the team
|
|
339
|
+
*/
|
|
340
|
+
async generateQueryFile(config) {
|
|
341
|
+
const templatePath = this.templateDiscovery.getTemplatePath('query');
|
|
342
|
+
if (!this.templateDiscovery.templateExists('query')) {
|
|
343
|
+
throw new Error(`Query template not found at: ${templatePath}`);
|
|
344
|
+
}
|
|
345
|
+
// Set up template variables
|
|
346
|
+
const variables = {
|
|
347
|
+
queryName: config.teamName,
|
|
348
|
+
targetType: 'team',
|
|
349
|
+
targetName: config.teamName,
|
|
350
|
+
projectName: config.projectName,
|
|
351
|
+
inputMessage: `Hello! Can you help me understand what the ${config.teamName} team can do for the ${config.projectName} project?`,
|
|
352
|
+
};
|
|
353
|
+
this.templateEngine.setVariables(variables);
|
|
354
|
+
// Process the query template file
|
|
355
|
+
const templateFilePath = path.join(templatePath, 'query.template.yaml');
|
|
356
|
+
const destinationFilePath = path.join(config.projectDirectory, 'queries', `${config.teamName}-query.yaml`);
|
|
357
|
+
await this.templateEngine.processFile(templateFilePath, destinationFilePath);
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Main generate method
|
|
361
|
+
*/
|
|
362
|
+
async generate(name, destination, options) {
|
|
363
|
+
console.log(chalk.blue(`š„ ARK Team Generator\n`));
|
|
364
|
+
try {
|
|
365
|
+
// Get team configuration
|
|
366
|
+
const config = await this.getTeamConfig(name, destination, options);
|
|
367
|
+
console.log(chalk.cyan(`š Team Configuration:`));
|
|
368
|
+
console.log(chalk.gray(` Name: ${config.teamName}`));
|
|
369
|
+
console.log(chalk.gray(` Project: ${config.projectName}`));
|
|
370
|
+
console.log(chalk.gray(` Strategy: ${config.strategy}`));
|
|
371
|
+
console.log(chalk.gray(` Members: ${config.members.length} agent(s)`));
|
|
372
|
+
config.members.forEach((member) => {
|
|
373
|
+
console.log(chalk.gray(` - ${member.name}`));
|
|
374
|
+
});
|
|
375
|
+
console.log(chalk.gray(` Directory: ${config.projectDirectory}\n`));
|
|
376
|
+
// Generate the team
|
|
377
|
+
console.log(chalk.blue(`š§ Generating team: ${config.teamName}`));
|
|
378
|
+
await this.generateTeamFile(config);
|
|
379
|
+
// Generate query if requested
|
|
380
|
+
if (config.createQuery) {
|
|
381
|
+
console.log(chalk.blue(`š§ Generating query for team: ${config.teamName}`));
|
|
382
|
+
await this.generateQueryFile(config);
|
|
383
|
+
}
|
|
384
|
+
console.log(chalk.green(`\nā
Successfully generated team: ${config.teamName}`));
|
|
385
|
+
console.log(chalk.gray(`š Created: teams/${config.teamName}-team.yaml`));
|
|
386
|
+
if (config.createQuery) {
|
|
387
|
+
console.log(chalk.gray(`š Created: queries/${config.teamName}-query.yaml`));
|
|
388
|
+
}
|
|
389
|
+
// Show next steps
|
|
390
|
+
console.log(chalk.cyan(`\nš Next Steps:`));
|
|
391
|
+
console.log(chalk.gray(` 1. Review and customise the team configuration`));
|
|
392
|
+
if (config.createQuery) {
|
|
393
|
+
console.log(chalk.gray(` 2. Review and customise the query configuration`));
|
|
394
|
+
console.log(chalk.gray(` 3. Deploy with: helm upgrade --install ${config.projectName} .`));
|
|
395
|
+
console.log(chalk.gray(` 4. Test with: kubectl get teams,queries`));
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
console.log(chalk.gray(` 2. Deploy with: helm upgrade --install ${config.projectName} .`));
|
|
399
|
+
console.log(chalk.gray(` 3. Test with: kubectl get teams`));
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
console.error(chalk.red(`\nā Failed to generate team:`), error);
|
|
404
|
+
throw error;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
export interface GeneratorOptions {
|
|
3
|
+
name?: string;
|
|
4
|
+
destination?: string;
|
|
5
|
+
interactive?: boolean;
|
|
6
|
+
projectType?: string;
|
|
7
|
+
namespace?: string;
|
|
8
|
+
skipModels?: boolean;
|
|
9
|
+
skipGit?: boolean;
|
|
10
|
+
selectedModels?: string;
|
|
11
|
+
azureApiKey?: string;
|
|
12
|
+
azureBaseUrl?: string;
|
|
13
|
+
aigwBaseUrl?: string;
|
|
14
|
+
gitUserName?: string;
|
|
15
|
+
gitUserEmail?: string;
|
|
16
|
+
gitCreateCommit?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface Generator {
|
|
19
|
+
name: string;
|
|
20
|
+
description: string;
|
|
21
|
+
templatePath: string;
|
|
22
|
+
generate(name: string, destination: string, options: GeneratorOptions): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
export declare function createGenerateCommand(): Command;
|