@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.
Files changed (76) hide show
  1. package/README.md +95 -0
  2. package/dist/commands/cluster/get-ip.d.ts +2 -0
  3. package/dist/commands/cluster/get-ip.js +32 -0
  4. package/dist/commands/cluster/get-type.d.ts +2 -0
  5. package/dist/commands/cluster/get-type.js +26 -0
  6. package/dist/commands/cluster/index.d.ts +2 -0
  7. package/dist/commands/cluster/index.js +10 -0
  8. package/dist/commands/completion.d.ts +2 -0
  9. package/dist/commands/completion.js +108 -0
  10. package/dist/commands/config.d.ts +5 -0
  11. package/dist/commands/config.js +327 -0
  12. package/dist/commands/generate/config.d.ts +145 -0
  13. package/dist/commands/generate/config.js +253 -0
  14. package/dist/commands/generate/generators/agent.d.ts +2 -0
  15. package/dist/commands/generate/generators/agent.js +156 -0
  16. package/dist/commands/generate/generators/index.d.ts +6 -0
  17. package/dist/commands/generate/generators/index.js +6 -0
  18. package/dist/commands/generate/generators/marketplace.d.ts +2 -0
  19. package/dist/commands/generate/generators/marketplace.js +304 -0
  20. package/dist/commands/generate/generators/mcpserver.d.ts +25 -0
  21. package/dist/commands/generate/generators/mcpserver.js +350 -0
  22. package/dist/commands/generate/generators/project.d.ts +2 -0
  23. package/dist/commands/generate/generators/project.js +784 -0
  24. package/dist/commands/generate/generators/query.d.ts +2 -0
  25. package/dist/commands/generate/generators/query.js +213 -0
  26. package/dist/commands/generate/generators/team.d.ts +2 -0
  27. package/dist/commands/generate/generators/team.js +407 -0
  28. package/dist/commands/generate/index.d.ts +24 -0
  29. package/dist/commands/generate/index.js +357 -0
  30. package/dist/commands/generate/templateDiscovery.d.ts +30 -0
  31. package/dist/commands/generate/templateDiscovery.js +94 -0
  32. package/dist/commands/generate/templateEngine.d.ts +78 -0
  33. package/dist/commands/generate/templateEngine.js +368 -0
  34. package/dist/commands/generate/utils/nameUtils.d.ts +35 -0
  35. package/dist/commands/generate/utils/nameUtils.js +110 -0
  36. package/dist/commands/generate/utils/projectUtils.d.ts +28 -0
  37. package/dist/commands/generate/utils/projectUtils.js +133 -0
  38. package/dist/components/DashboardCLI.d.ts +3 -0
  39. package/dist/components/DashboardCLI.js +149 -0
  40. package/dist/components/GeneratorUI.d.ts +3 -0
  41. package/dist/components/GeneratorUI.js +167 -0
  42. package/dist/components/statusChecker.d.ts +48 -0
  43. package/dist/components/statusChecker.js +251 -0
  44. package/dist/config.d.ts +42 -0
  45. package/dist/config.js +243 -0
  46. package/dist/index.d.ts +2 -0
  47. package/dist/index.js +67 -0
  48. package/dist/lib/arkClient.d.ts +32 -0
  49. package/dist/lib/arkClient.js +43 -0
  50. package/dist/lib/cluster.d.ts +8 -0
  51. package/dist/lib/cluster.js +134 -0
  52. package/dist/lib/config.d.ts +82 -0
  53. package/dist/lib/config.js +223 -0
  54. package/dist/lib/consts.d.ts +10 -0
  55. package/dist/lib/consts.js +15 -0
  56. package/dist/lib/errors.d.ts +56 -0
  57. package/dist/lib/errors.js +208 -0
  58. package/dist/lib/exec.d.ts +5 -0
  59. package/dist/lib/exec.js +20 -0
  60. package/dist/lib/gatewayManager.d.ts +24 -0
  61. package/dist/lib/gatewayManager.js +85 -0
  62. package/dist/lib/kubernetes.d.ts +28 -0
  63. package/dist/lib/kubernetes.js +122 -0
  64. package/dist/lib/progress.d.ts +128 -0
  65. package/dist/lib/progress.js +273 -0
  66. package/dist/lib/security.d.ts +37 -0
  67. package/dist/lib/security.js +295 -0
  68. package/dist/lib/types.d.ts +37 -0
  69. package/dist/lib/types.js +1 -0
  70. package/dist/lib/wrappers/git.d.ts +2 -0
  71. package/dist/lib/wrappers/git.js +43 -0
  72. package/dist/ui/MainMenu.d.ts +3 -0
  73. package/dist/ui/MainMenu.js +116 -0
  74. package/dist/ui/statusFormatter.d.ts +9 -0
  75. package/dist/ui/statusFormatter.js +47 -0
  76. package/package.json +62 -0
@@ -0,0 +1,2 @@
1
+ import { Generator } from '../index.js';
2
+ export declare function createQueryGenerator(): Generator;
@@ -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,2 @@
1
+ import { Generator } from '../index.js';
2
+ export declare function createTeamGenerator(): Generator;
@@ -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;