sdd-toolkit 1.0.0 → 1.1.0

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/src/index.js CHANGED
@@ -1,183 +1,207 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require('fs');
4
- const fsp = require('fs/promises');
5
- const path = require('path');
6
- const { intro, outro, multiselect, spinner, note } = require('@clack/prompts');
7
- const pc = require('picocolors');
8
-
9
- // Módulos Internos
10
- const { loadAgents } = require('./lib/agents');
11
- const {
12
- toGeminiTOML,
13
- toRooConfig,
14
- toKiloMarkdown,
15
- toCopilotInstructions,
16
- toCursorMDC,
17
- toWindsurfRules,
18
- toPlainSystemPrompt,
19
- toTraeRules
20
- } = require('./lib/transformers');
21
- const { generateWorkflowGuide } = require('./lib/docs');
22
-
23
- async function main() {
24
- console.clear();
25
- intro(pc.bgMagenta(pc.white(' UNIVERSAL SPEC CLI ')));
26
-
27
- // 1. Scaffold Automático (Sempre executa)
28
- const created = generateWorkflowGuide(process.cwd());
29
- if (created) {
30
- console.log(pc.green('✔ Estrutura de pastas (docs/) verificada.'));
31
- }
32
-
33
- // 2. Seleção de Ferramentas (Múltipla escolha)
34
- const tools = await multiselect({
35
- message: 'Para quais ferramentas você deseja instalar os Agentes?',
36
- options: [
37
- { value: 'gemini', label: 'Gemini CLI', hint: '.gemini/commands/dev' },
38
- { value: 'roo', label: 'Roo Code', hint: '.roo/ & custom_modes.json' },
39
- { value: 'cline', label: 'Cline', hint: '.cline/ & custom_modes.json' },
40
- { value: 'cursor', label: 'Cursor', hint: '.cursor/rules/*.mdc' },
41
- { value: 'windsurf', label: 'Windsurf', hint: '.windsurfrules' },
42
- { value: 'trae', label: 'Trae IDE', hint: '.trae/instructions.md' },
43
- { value: 'kilo', label: 'Kilo Code', hint: '.kilo/prompts/*.md' },
44
- { value: 'copilot', label: 'GitHub Copilot', hint: '.github/copilot-instructions.md' },
45
- { value: 'web', label: 'OpenAI / Claude', hint: 'prompts/*.txt' },
46
- { value: 'opencode', label: 'OpenCode', hint: '.opencode/*.md' },
47
- ],
48
- required: true,
49
- hint: 'Espaço para selecionar, Enter para confirmar'
50
- });
51
-
52
- if (!tools || tools.length === 0) {
53
- outro('Nenhuma ferramenta selecionada. Operação cancelada.');
54
- process.exit(0);
55
- }
56
-
57
- await processAgentsInstallation(tools);
58
-
59
- outro(pc.green('Configuração concluída com sucesso! 🚀'));
60
- }
61
-
62
- async function processAgentsInstallation(tools) {
63
- const s = spinner();
64
- s.start('Carregando definições...');
65
-
66
- try {
67
- const validAgents = await loadAgents();
68
-
69
- if (validAgents.length === 0) {
70
- s.stop('Nenhum agente válido encontrado.');
71
- return;
72
- }
73
-
74
- s.message(`Instalando agentes para: ${tools.join(', ')}...`);
75
-
76
- // Itera sobre cada ferramenta selecionada
77
- for (const tool of tools) {
78
-
79
- // Instalação Específica por Ferramenta
80
- if (tool === 'gemini') {
81
- const targetDir = path.join(process.cwd(), '.gemini', 'commands', 'dev');
82
- await fsp.mkdir(targetDir, { recursive: true });
83
-
84
- await Promise.all(validAgents.map(agent => {
85
- const toml = toGeminiTOML(agent);
86
- const fileName = `${agent.originalName}.toml`;
87
- return fsp.writeFile(path.join(targetDir, fileName), toml);
88
- }));
89
- }
90
- else if (tool === 'roo' || tool === 'cline') {
91
- const configDir = tool === 'roo' ? '.roo' : '.cline';
92
- const targetDir = path.join(process.cwd(), configDir);
93
- await fsp.mkdir(targetDir, { recursive: true });
94
-
95
- await Promise.all(validAgents.map(agent => {
96
- const md = toKiloMarkdown(agent);
97
- return fsp.writeFile(path.join(targetDir, `${agent.slug}.md`), md);
98
- }));
99
-
100
- const modes = validAgents.map(agent => toRooConfig(agent, agent.slug));
101
- const jsonContent = JSON.stringify({ customModes: modes }, null, 2);
102
- const fileName = `${tool}_custom_modes.json`;
103
- await fsp.writeFile(path.join(process.cwd(), fileName), jsonContent);
104
- }
105
- else if (tool === 'kilo') {
106
- const targetDir = path.join(process.cwd(), '.kilo', 'prompts');
107
- await fsp.mkdir(targetDir, { recursive: true });
108
-
109
- await Promise.all(validAgents.map(agent => {
110
- const md = toKiloMarkdown(agent);
111
- return fsp.writeFile(path.join(targetDir, `${agent.slug}.md`), md);
112
- }));
113
- }
114
- else if (tool === 'copilot') {
115
- const githubDir = path.join(process.cwd(), '.github');
116
- const agentsDir = path.join(githubDir, 'agents');
117
- await fsp.mkdir(agentsDir, { recursive: true });
118
-
119
- await Promise.all(validAgents.map(agent => {
120
- const md = toCopilotInstructions(agent);
121
- return fsp.writeFile(path.join(agentsDir, `${agent.slug}.md`), md);
122
- }));
123
-
124
- const mainAgent = validAgents.find(a => a.slug.includes('coder')) || validAgents[0];
125
- const mainInstructions = toCopilotInstructions(mainAgent);
126
- await fsp.writeFile(path.join(githubDir, 'copilot-instructions.md'), mainInstructions);
127
- }
128
- else if (tool === 'cursor') {
129
- const rulesDir = path.join(process.cwd(), '.cursor', 'rules');
130
- await fsp.mkdir(rulesDir, { recursive: true });
131
-
132
- await Promise.all(validAgents.map(agent => {
133
- const mdc = toCursorMDC(agent);
134
- return fsp.writeFile(path.join(rulesDir, `${agent.slug}.mdc`), mdc);
135
- }));
136
- }
137
- else if (tool === 'windsurf') {
138
- const mainAgent = validAgents.find(a => a.slug.includes('coder')) || validAgents[0];
139
- const rules = toWindsurfRules(mainAgent);
140
- await fsp.writeFile(path.join(process.cwd(), '.windsurfrules'), rules);
141
- }
142
- else if (tool === 'trae') {
143
- const traeDir = path.join(process.cwd(), '.trae');
144
- await fsp.mkdir(traeDir, { recursive: true });
145
-
146
- const mainAgent = validAgents.find(a => a.slug.includes('coder')) || validAgents[0];
147
- const rules = toTraeRules(mainAgent);
148
- await fsp.writeFile(path.join(traeDir, 'instructions.md'), rules);
149
- }
150
- else if (tool === 'web') {
151
- const targetDir = path.join(process.cwd(), 'prompts');
152
- await fsp.mkdir(targetDir, { recursive: true });
153
-
154
- await Promise.all(validAgents.map(agent => {
155
- const txt = toPlainSystemPrompt(agent);
156
- return fsp.writeFile(path.join(targetDir, `${agent.slug}.txt`), txt);
157
- }));
158
- }
159
- else if (tool === 'opencode') {
160
- const targetDir = path.join(process.cwd(), '.opencode');
161
- await fsp.mkdir(targetDir, { recursive: true });
162
-
163
- await Promise.all(validAgents.map(agent => {
164
- const md = toKiloMarkdown(agent);
165
- return fsp.writeFile(path.join(targetDir, `${agent.slug}.md`), md);
166
- }));
167
- }
168
- }
169
-
170
- s.stop('Instalação finalizada!');
171
-
172
- // Feedback consolidado
173
- if (tools.includes('roo') || tools.includes('cline')) {
174
- note('Lembre-se de configurar os Custom Modes no settings.json para Roo/Cline.', 'Aviso');
175
- }
176
-
177
- } catch (e) {
178
- s.stop('Falha');
179
- console.error(pc.red(e.message));
180
- }
181
- }
182
-
183
- main().catch(console.error);
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const fsp = require('fs/promises');
5
+ const path = require('path');
6
+ const { intro, outro, multiselect, spinner, note, select, text } = require('@clack/prompts');
7
+ const pc = require('picocolors');
8
+
9
+ // Módulos Internos
10
+ const { loadAgents } = require('./lib/agents');
11
+ const { STACK_PROFILES } = require('./lib/profiles');
12
+ const {
13
+ toGeminiTOML,
14
+ toRooConfig,
15
+ toKiloMarkdown,
16
+ toCopilotInstructions,
17
+ toCursorMDC,
18
+ toWindsurfRules,
19
+ toPlainSystemPrompt,
20
+ toTraeRules
21
+ } = require('./lib/transformers');
22
+ const { generateWorkflowGuide } = require('./lib/docs');
23
+
24
+ async function main() {
25
+ console.clear();
26
+ intro(pc.bgMagenta(pc.white(' UNIVERSAL SPEC CLI ')));
27
+
28
+ // 1. Scaffold Automático (Sempre executa)
29
+ const created = generateWorkflowGuide(process.cwd());
30
+ if (created) {
31
+ console.log(pc.green('✔ Estrutura de pastas (docs/) verificada.'));
32
+ }
33
+
34
+ // 2. Feature 5: Seleção de Stack (Profile)
35
+ const stackOptions = Object.entries(STACK_PROFILES).map(([key, profile]) => ({
36
+ value: key,
37
+ label: profile.label
38
+ }));
39
+
40
+ const stackProfile = await select({
41
+ message: 'Qual é o perfil da sua Stack tecnológica?',
42
+ options: stackOptions,
43
+ initialValue: 'generic'
44
+ });
45
+
46
+ if (typeof stackProfile === 'symbol') { process.exit(0); } // Handle Cancel
47
+
48
+ // 3. Feature 3: Regras Globais (Opcional)
49
+ const globalRules = await text({
50
+ message: 'Deseja adicionar alguma Regra Global para TODOS os agentes?',
51
+ placeholder: 'Ex: Responda sempre em pt-BR; Use Conventional Commits...',
52
+ required: false
53
+ });
54
+
55
+ if (typeof globalRules === 'symbol') { process.exit(0); } // Handle Cancel
56
+
57
+ // 4. Seleção de Ferramentas (Múltipla escolha)
58
+ const tools = await multiselect({
59
+ message: 'Para quais ferramentas você deseja instalar os Agentes?',
60
+ options: [
61
+ { value: 'gemini', label: 'Gemini CLI', hint: '.gemini/commands/dev' },
62
+ { value: 'roo', label: 'Roo Code', hint: '.roo/ & custom_modes.json' },
63
+ { value: 'cline', label: 'Cline', hint: '.cline/ & custom_modes.json' },
64
+ { value: 'cursor', label: 'Cursor', hint: '.cursor/rules/*.mdc' },
65
+ { value: 'windsurf', label: 'Windsurf', hint: '.windsurfrules' },
66
+ { value: 'trae', label: 'Trae IDE', hint: '.trae/instructions.md' },
67
+ { value: 'kilo', label: 'Kilo Code', hint: '.kilo/prompts/*.md' },
68
+ { value: 'copilot', label: 'GitHub Copilot', hint: '.github/copilot-instructions.md' },
69
+ { value: 'web', label: 'OpenAI / Claude', hint: 'prompts/*.txt' },
70
+ { value: 'opencode', label: 'OpenCode', hint: '.opencode/*.md' },
71
+ ],
72
+ required: true,
73
+ hint: 'Espaço para selecionar, Enter para confirmar'
74
+ });
75
+
76
+ if (!tools || tools.length === 0) {
77
+ outro('Nenhuma ferramenta selecionada. Operação cancelada.');
78
+ process.exit(0);
79
+ }
80
+
81
+ await processAgentsInstallation(tools, { stackProfile, globalRules });
82
+
83
+ outro(pc.green('Configuração concluída com sucesso! 🚀'));
84
+ }
85
+
86
+ async function processAgentsInstallation(tools, options) {
87
+ const s = spinner();
88
+ s.start('Carregando definições...');
89
+
90
+ try {
91
+ const validAgents = await loadAgents(options);
92
+
93
+ if (validAgents.length === 0) {
94
+ s.stop('Nenhum agente válido encontrado.');
95
+ return;
96
+ }
97
+
98
+ s.message(`Instalando agentes para: ${tools.join(', ')}...`);
99
+
100
+ // Itera sobre cada ferramenta selecionada
101
+ for (const tool of tools) {
102
+
103
+ // Instalação Específica por Ferramenta
104
+ if (tool === 'gemini') {
105
+ const targetDir = path.join(process.cwd(), '.gemini', 'commands', 'dev');
106
+ await fsp.mkdir(targetDir, { recursive: true });
107
+
108
+ await Promise.all(validAgents.map(agent => {
109
+ const toml = toGeminiTOML(agent);
110
+ const fileName = `${agent.originalName}.toml`;
111
+ return fsp.writeFile(path.join(targetDir, fileName), toml);
112
+ }));
113
+ }
114
+ else if (tool === 'roo' || tool === 'cline') {
115
+ const configDir = tool === 'roo' ? '.roo' : '.cline';
116
+ const targetDir = path.join(process.cwd(), configDir);
117
+ await fsp.mkdir(targetDir, { recursive: true });
118
+
119
+ await Promise.all(validAgents.map(agent => {
120
+ const md = toKiloMarkdown(agent);
121
+ return fsp.writeFile(path.join(targetDir, `${agent.slug}.md`), md);
122
+ }));
123
+
124
+ const modes = validAgents.map(agent => toRooConfig(agent, agent.slug));
125
+ const jsonContent = JSON.stringify({ customModes: modes }, null, 2);
126
+ const fileName = `${tool}_custom_modes.json`;
127
+ await fsp.writeFile(path.join(process.cwd(), fileName), jsonContent);
128
+ }
129
+ else if (tool === 'kilo') {
130
+ const targetDir = path.join(process.cwd(), '.kilo', 'prompts');
131
+ await fsp.mkdir(targetDir, { recursive: true });
132
+
133
+ await Promise.all(validAgents.map(agent => {
134
+ const md = toKiloMarkdown(agent);
135
+ return fsp.writeFile(path.join(targetDir, `${agent.slug}.md`), md);
136
+ }));
137
+ }
138
+ else if (tool === 'copilot') {
139
+ const githubDir = path.join(process.cwd(), '.github');
140
+ const agentsDir = path.join(githubDir, 'agents');
141
+ await fsp.mkdir(agentsDir, { recursive: true });
142
+
143
+ await Promise.all(validAgents.map(agent => {
144
+ const md = toCopilotInstructions(agent);
145
+ return fsp.writeFile(path.join(agentsDir, `${agent.slug}.md`), md);
146
+ }));
147
+
148
+ const mainAgent = validAgents.find(a => a.slug.includes('coder')) || validAgents[0];
149
+ const mainInstructions = toCopilotInstructions(mainAgent);
150
+ await fsp.writeFile(path.join(githubDir, 'copilot-instructions.md'), mainInstructions);
151
+ }
152
+ else if (tool === 'cursor') {
153
+ const rulesDir = path.join(process.cwd(), '.cursor', 'rules');
154
+ await fsp.mkdir(rulesDir, { recursive: true });
155
+
156
+ await Promise.all(validAgents.map(agent => {
157
+ const mdc = toCursorMDC(agent);
158
+ return fsp.writeFile(path.join(rulesDir, `${agent.slug}.mdc`), mdc);
159
+ }));
160
+ }
161
+ else if (tool === 'windsurf') {
162
+ const mainAgent = validAgents.find(a => a.slug.includes('coder')) || validAgents[0];
163
+ const rules = toWindsurfRules(mainAgent);
164
+ await fsp.writeFile(path.join(process.cwd(), '.windsurfrules'), rules);
165
+ }
166
+ else if (tool === 'trae') {
167
+ const traeDir = path.join(process.cwd(), '.trae');
168
+ await fsp.mkdir(traeDir, { recursive: true });
169
+
170
+ const mainAgent = validAgents.find(a => a.slug.includes('coder')) || validAgents[0];
171
+ const rules = toTraeRules(mainAgent);
172
+ await fsp.writeFile(path.join(traeDir, 'instructions.md'), rules);
173
+ }
174
+ else if (tool === 'web') {
175
+ const targetDir = path.join(process.cwd(), 'prompts');
176
+ await fsp.mkdir(targetDir, { recursive: true });
177
+
178
+ await Promise.all(validAgents.map(agent => {
179
+ const txt = toPlainSystemPrompt(agent);
180
+ return fsp.writeFile(path.join(targetDir, `${agent.slug}.txt`), txt);
181
+ }));
182
+ }
183
+ else if (tool === 'opencode') {
184
+ const targetDir = path.join(process.cwd(), '.opencode');
185
+ await fsp.mkdir(targetDir, { recursive: true });
186
+
187
+ await Promise.all(validAgents.map(agent => {
188
+ const md = toKiloMarkdown(agent);
189
+ return fsp.writeFile(path.join(targetDir, `${agent.slug}.md`), md);
190
+ }));
191
+ }
192
+ }
193
+
194
+ s.stop('Instalação finalizada!');
195
+
196
+ // Feedback consolidado
197
+ if (tools.includes('roo') || tools.includes('cline')) {
198
+ note('Lembre-se de configurar os Custom Modes no settings.json para Roo/Cline.', 'Aviso');
199
+ }
200
+
201
+ } catch (e) {
202
+ s.stop('Falha');
203
+ console.error(pc.red(e.message));
204
+ }
205
+ }
206
+
207
+ main().catch(console.error);
package/src/lib/agents.js CHANGED
@@ -1,68 +1,125 @@
1
- const fs = require('fs/promises');
2
- const path = require('path');
3
- const yaml = require('js-yaml');
4
- const { AgentSchema } = require('./schema');
5
- const pc = require('picocolors');
6
-
7
- /**
8
- * Carrega e valida todas as definições de agentes da pasta definitions
9
- * @returns {Promise<Array>} Lista de agentes validados
10
- */
11
- async function loadAgents() {
12
- const definitionsDir = path.join(__dirname, '..', '..', 'definitions');
13
-
14
- try {
15
- await fs.access(definitionsDir);
16
- } catch {
17
- throw new Error(`Pasta de definições não encontrada: ${definitionsDir}`);
18
- }
19
-
20
- const files = await fs.readdir(definitionsDir);
21
- const yamlFiles = files.filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
22
-
23
- // Leitura e processamento em paralelo
24
- const results = await Promise.all(yamlFiles.map(async (file) => {
25
- try {
26
- const content = await fs.readFile(path.join(definitionsDir, file), 'utf8');
27
- const raw = yaml.load(content);
28
-
29
- const parsed = AgentSchema.safeParse(raw);
30
- if (!parsed.success) {
31
- return {
32
- success: false,
33
- file,
34
- error: 'Validação do Schema falhou',
35
- details: parsed.error.format()
36
- };
37
- }
38
-
39
- const agent = parsed.data;
40
- // Normaliza o slug: dev.coder -> dev-coder
41
- agent.slug = file.replace(/\.ya?ml$/, '').replace(/\./g, '-');
42
- // Mantém o nome original do arquivo para referência (útil para Gemini CLI)
43
- agent.originalName = file.replace(/\.ya?ml$/, '');
44
-
45
- return { success: true, agent };
46
-
47
- } catch (e) {
48
- return { success: false, file, error: e.message };
49
- }
50
- }));
51
-
52
- // Separa sucessos e falhas
53
- const validAgents = [];
54
- const errors = [];
55
-
56
- results.forEach(res => {
57
- if (res.success) {
58
- validAgents.push(res.agent);
59
- } else {
60
- errors.push(res);
61
- console.warn(pc.yellow(`⚠️ Ignorando ${res.file}: ${res.error}`));
62
- }
63
- });
64
-
65
- return validAgents;
66
- }
67
-
68
- module.exports = { loadAgents };
1
+ const fs = require('fs/promises');
2
+ const path = require('path');
3
+ const yaml = require('js-yaml');
4
+ const { AgentSchema } = require('./schema');
5
+ const { STACK_PROFILES } = require('./profiles');
6
+ const pc = require('picocolors');
7
+
8
+ /**
9
+ * Carrega e valida todas as definições de agentes da pasta definitions
10
+ * Suporta sobrescrita local (.sdd/agents) e injeção de regras
11
+ *
12
+ * @param {Object} options
13
+ * @param {string} options.stackProfile - Chave do perfil de stack (ex: 'frontend-react')
14
+ * @param {string} options.globalRules - Regras globais separadas por quebra de linha
15
+ * @returns {Promise<Array>} Lista de agentes validados
16
+ */
17
+ async function loadAgents(options = {}) {
18
+ const definitionsDir = path.join(__dirname, '..', '..', 'definitions');
19
+ const localDefinitionsDir = path.join(process.cwd(), '.sdd', 'agents');
20
+
21
+ // Verifica diretório padrão
22
+ try {
23
+ await fs.access(definitionsDir);
24
+ } catch {
25
+ throw new Error(`Pasta de definições padrão não encontrada: ${definitionsDir}`);
26
+ }
27
+
28
+ // Identifica arquivos padrão
29
+ const files = await fs.readdir(definitionsDir);
30
+ const yamlFiles = files.filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
31
+
32
+ // Verifica se existe diretório de sobrescrita local
33
+ let hasLocalOverrides = false;
34
+ try {
35
+ await fs.access(localDefinitionsDir);
36
+ hasLocalOverrides = true;
37
+ } catch {
38
+ // Ignora se não existir
39
+ }
40
+
41
+ // Leitura e processamento em paralelo
42
+ const results = await Promise.all(yamlFiles.map(async (file) => {
43
+ try {
44
+ let content;
45
+ let source = 'default';
46
+
47
+ // Feature 2: Sobrescrita Local
48
+ if (hasLocalOverrides) {
49
+ try {
50
+ const localPath = path.join(localDefinitionsDir, file);
51
+ await fs.access(localPath);
52
+ content = await fs.readFile(localPath, 'utf8');
53
+ source = 'local';
54
+ } catch {
55
+ // Arquivo local não existe, usa padrão
56
+ }
57
+ }
58
+
59
+ if (!content) {
60
+ content = await fs.readFile(path.join(definitionsDir, file), 'utf8');
61
+ }
62
+
63
+ const raw = yaml.load(content);
64
+
65
+ const parsed = AgentSchema.safeParse(raw);
66
+ if (!parsed.success) {
67
+ return {
68
+ success: false,
69
+ file,
70
+ error: 'Validação do Schema falhou',
71
+ details: parsed.error.format()
72
+ };
73
+ }
74
+
75
+ const agent = parsed.data;
76
+
77
+ // Normaliza o slug
78
+ agent.slug = file.replace(/\.ya?ml$/, '').replace(/\./g, '-');
79
+ agent.originalName = file.replace(/\.ya?ml$/, '');
80
+ agent.source = source; // Metadata útil para logs
81
+
82
+ // Feature 5: Injeção de Regras de Stack
83
+ if (options.stackProfile && STACK_PROFILES[options.stackProfile]) {
84
+ const stackRules = STACK_PROFILES[options.stackProfile].rules;
85
+ if (stackRules && stackRules.length > 0) {
86
+ agent.rules = [...agent.rules, ...stackRules];
87
+ }
88
+ }
89
+
90
+ // Feature 3: Injeção de Regras Globais
91
+ if (options.globalRules && typeof options.globalRules === 'string') {
92
+ const customRules = options.globalRules
93
+ .split('\n')
94
+ .map(r => r.trim())
95
+ .filter(r => r.length > 0);
96
+
97
+ if (customRules.length > 0) {
98
+ agent.rules = [...agent.rules, ...customRules];
99
+ }
100
+ }
101
+
102
+ return { success: true, agent };
103
+
104
+ } catch (e) {
105
+ return { success: false, file, error: e.message };
106
+ }
107
+ }));
108
+
109
+ // Separa sucessos e falhas
110
+ const validAgents = [];
111
+ const errors = [];
112
+
113
+ results.forEach(res => {
114
+ if (res.success) {
115
+ validAgents.push(res.agent);
116
+ } else {
117
+ errors.push(res);
118
+ console.warn(pc.yellow(`⚠️ Ignorando ${res.file}: ${res.error}`));
119
+ }
120
+ });
121
+
122
+ return validAgents;
123
+ }
124
+
125
+ module.exports = { loadAgents };