@tacuchi/agent-factory 0.1.1 → 0.2.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/bin/agent-factory.js +4 -0
- package/package.json +1 -1
- package/src/commands/create.js +70 -28
- package/src/commands/list.js +30 -2
- package/src/core/agent-writer.js +19 -5
- package/src/utils/prompts.js +36 -9
- package/templates/roles/coordinator.md.tmpl +18 -4
package/bin/agent-factory.js
CHANGED
|
@@ -39,6 +39,10 @@ program
|
|
|
39
39
|
.option('-o, --output <path>', 'Output directory (default: current dir)')
|
|
40
40
|
.option('-t, --target <target>', 'Target: claude, codex, all', 'all')
|
|
41
41
|
.option('--tools <tools>', 'Comma-separated tools: Read,Write,Edit,Bash')
|
|
42
|
+
.option('--specialists <list>', 'CSV list of specialist agent names (for coordinator role)')
|
|
43
|
+
.option('--repo-count <n>', 'Number of repos in workspace (for coordinator role)', parseInt)
|
|
44
|
+
.option('--description <text>', 'Short description for the agent (for custom role)')
|
|
45
|
+
.option('--instructions <text>', 'Agent body: inline text or path to .md file (for custom role)')
|
|
42
46
|
.option('-y, --yes', 'Skip confirmations')
|
|
43
47
|
.action(async (options) => {
|
|
44
48
|
const { runCreate } = require('../src/commands/create');
|
package/package.json
CHANGED
package/src/commands/create.js
CHANGED
|
@@ -10,37 +10,48 @@ async function runCreate(options = {}) {
|
|
|
10
10
|
const isInteractive = !options.name;
|
|
11
11
|
const config = isInteractive ? await askCreateOptions() : normalizeFlags(options);
|
|
12
12
|
|
|
13
|
-
const { name, role, model, scope, output, target, tools } = config;
|
|
13
|
+
const { name, role, model, scope, output, target, tools, specialists, repoCount, description, instructions } = config;
|
|
14
14
|
|
|
15
15
|
const spin = spinner('Generating agent...').start();
|
|
16
16
|
|
|
17
|
-
let
|
|
18
|
-
if (scope) {
|
|
19
|
-
stackResult = await detect(scope);
|
|
20
|
-
}
|
|
17
|
+
let body;
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
name,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
verify_cmds: stackResult.verifyCommands || 'N/A',
|
|
31
|
-
stack_csv: stackResult.stackCsv,
|
|
32
|
-
};
|
|
19
|
+
if (role === 'custom') {
|
|
20
|
+
body = await resolveCustomBody(instructions, name, description);
|
|
21
|
+
spin.succeed('Custom agent generated');
|
|
22
|
+
} else {
|
|
23
|
+
let stackResult = { primaryTech: 'Generic', framework: '', verifyCommands: '', stackParts: [], stackCsv: 'Generic' };
|
|
24
|
+
if (scope) {
|
|
25
|
+
stackResult = await detect(scope);
|
|
26
|
+
}
|
|
33
27
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
28
|
+
const templateData = {
|
|
29
|
+
name,
|
|
30
|
+
primary_tech: stackResult.primaryTech,
|
|
31
|
+
framework: stackResult.framework,
|
|
32
|
+
scope: scope || '.',
|
|
33
|
+
stack_list: stackResult.stackParts.length > 0
|
|
34
|
+
? stackResult.stackParts.map((p) => `- ${p}`).join('\n')
|
|
35
|
+
: `- ${stackResult.primaryTech}`,
|
|
36
|
+
verify_cmds: stackResult.verifyCommands || 'N/A',
|
|
37
|
+
stack_csv: stackResult.stackCsv,
|
|
38
|
+
specialist_list: formatSpecialistList(specialists),
|
|
39
|
+
N: repoCount ? String(repoCount) : '',
|
|
40
|
+
repos_word: repoCount === 1 ? 'repositorio' : 'repositorios',
|
|
41
|
+
skills_section: '',
|
|
42
|
+
mcp_section: '',
|
|
43
|
+
};
|
|
42
44
|
|
|
43
|
-
|
|
45
|
+
const templateFile = `${role}.md.tmpl`;
|
|
46
|
+
try {
|
|
47
|
+
body = await renderFile(templateFile, templateData);
|
|
48
|
+
} catch {
|
|
49
|
+
spin.fail(`Template not found: ${templateFile}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
spin.succeed('Agent generated');
|
|
54
|
+
}
|
|
44
55
|
|
|
45
56
|
const outputDir = output || process.cwd();
|
|
46
57
|
const targetDirs = [];
|
|
@@ -64,6 +75,7 @@ async function runCreate(options = {}) {
|
|
|
64
75
|
body,
|
|
65
76
|
outputDir,
|
|
66
77
|
target,
|
|
78
|
+
description: description || undefined,
|
|
67
79
|
});
|
|
68
80
|
|
|
69
81
|
const validator = new AgentValidator();
|
|
@@ -72,14 +84,18 @@ async function runCreate(options = {}) {
|
|
|
72
84
|
const content = await fs.readFile(results.claude, 'utf8');
|
|
73
85
|
const validation = validator.validate(content, `${name}.md`);
|
|
74
86
|
if (validation.valid) {
|
|
75
|
-
log.success(`Claude:
|
|
87
|
+
log.success(`Claude: ${results.claude} (score: ${validation.score}/100)`);
|
|
76
88
|
} else {
|
|
77
|
-
log.warn(`Claude:
|
|
89
|
+
log.warn(`Claude: ${results.claude} (score: ${validation.score}/100, ${validation.errorCount} errors)`);
|
|
78
90
|
}
|
|
79
91
|
}
|
|
80
92
|
|
|
81
93
|
if (results.codex) {
|
|
82
|
-
log.success(`Codex:
|
|
94
|
+
log.success(`Codex: ${results.codex}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (results.skills) {
|
|
98
|
+
log.success(`Skills: ${results.skills}`);
|
|
83
99
|
}
|
|
84
100
|
}
|
|
85
101
|
|
|
@@ -93,7 +109,33 @@ function normalizeFlags(options) {
|
|
|
93
109
|
target: options.target || 'all',
|
|
94
110
|
tools: options.tools || '',
|
|
95
111
|
yes: options.yes || false,
|
|
112
|
+
specialists: options.specialists || '',
|
|
113
|
+
repoCount: options.repoCount || 0,
|
|
114
|
+
description: options.description || '',
|
|
115
|
+
instructions: options.instructions || '',
|
|
96
116
|
};
|
|
97
117
|
}
|
|
98
118
|
|
|
119
|
+
function formatSpecialistList(csv) {
|
|
120
|
+
if (!csv) return '';
|
|
121
|
+
const names = csv.split(',').map((n) => n.trim()).filter(Boolean);
|
|
122
|
+
if (names.length === 0) return '';
|
|
123
|
+
if (names.length === 1) return `\`${names[0]}\``;
|
|
124
|
+
const last = names.pop();
|
|
125
|
+
return names.map((n) => `\`${n}\``).join(', ') + ` y \`${last}\``;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function resolveCustomBody(instructions, name, description) {
|
|
129
|
+
if (!instructions) {
|
|
130
|
+
return `# ${name}\n\n${description || 'Custom agent.'}\n`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const fs = require('fs-extra');
|
|
134
|
+
if (instructions.endsWith('.md') && await fs.pathExists(instructions)) {
|
|
135
|
+
return await fs.readFile(instructions, 'utf8');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return `# ${name}\n\n${instructions}\n`;
|
|
139
|
+
}
|
|
140
|
+
|
|
99
141
|
module.exports = { runCreate };
|
package/src/commands/list.js
CHANGED
|
@@ -32,6 +32,24 @@ async function runList(dirPath, options = {}) {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
const skillsDir = path.join(abs, '.agents', 'skills');
|
|
36
|
+
if (await fs.pathExists(skillsDir)) {
|
|
37
|
+
const skillDirs = (await fs.readdir(skillsDir, { withFileTypes: true })).filter((d) => d.isDirectory());
|
|
38
|
+
for (const dir of skillDirs) {
|
|
39
|
+
const skillFile = path.join(skillsDir, dir.name, 'SKILL.md');
|
|
40
|
+
if (await fs.pathExists(skillFile)) {
|
|
41
|
+
const file = `${dir.name}.md`;
|
|
42
|
+
const existing = agents.find((a) => a.file === file);
|
|
43
|
+
if (existing) {
|
|
44
|
+
existing.skillsToo = true;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const content = await fs.readFile(skillFile, 'utf8');
|
|
48
|
+
agents.push({ file, source: '.agents/skills', content, format: 'skills' });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
35
53
|
if (agents.length === 0) {
|
|
36
54
|
if (options.json) {
|
|
37
55
|
console.log(JSON.stringify([]));
|
|
@@ -51,7 +69,7 @@ async function runList(dirPath, options = {}) {
|
|
|
51
69
|
return {
|
|
52
70
|
name: fm?.name || agent.file.replace('.md', ''),
|
|
53
71
|
model: fm?.model || null,
|
|
54
|
-
source: agent
|
|
72
|
+
source: resolveSource(agent),
|
|
55
73
|
score: validation.score,
|
|
56
74
|
valid: validation.valid,
|
|
57
75
|
};
|
|
@@ -72,7 +90,7 @@ async function runList(dirPath, options = {}) {
|
|
|
72
90
|
const fm = extractFrontmatter(agent.content);
|
|
73
91
|
const name = fm?.name || agent.file.replace('.md', '');
|
|
74
92
|
const model = fm?.model || '—';
|
|
75
|
-
const source = agent
|
|
93
|
+
const source = resolveSource(agent);
|
|
76
94
|
|
|
77
95
|
const validation = validator.validate(agent.content, agent.file);
|
|
78
96
|
const scoreStr = `${validation.score}/100`;
|
|
@@ -100,6 +118,16 @@ async function runList(dirPath, options = {}) {
|
|
|
100
118
|
console.log('');
|
|
101
119
|
}
|
|
102
120
|
|
|
121
|
+
function resolveSource(agent) {
|
|
122
|
+
const parts = [];
|
|
123
|
+
if (agent.source === '.claude/agents' || agent.codexToo) parts.push('claude');
|
|
124
|
+
if (agent.source === '.agents' || agent.codexToo) parts.push('codex');
|
|
125
|
+
if (agent.skillsToo || agent.source === '.agents/skills') parts.push('skills');
|
|
126
|
+
if (parts.length === 3) return 'all';
|
|
127
|
+
if (parts.length === 0) return agent.source;
|
|
128
|
+
return parts.join('+');
|
|
129
|
+
}
|
|
130
|
+
|
|
103
131
|
function padRow(name, model, source, score) {
|
|
104
132
|
return ` ${name.padEnd(30)} ${model.padEnd(10)} ${source.padEnd(18)} ${score}`;
|
|
105
133
|
}
|
package/src/core/agent-writer.js
CHANGED
|
@@ -4,9 +4,10 @@ const yaml = require('js-yaml');
|
|
|
4
4
|
|
|
5
5
|
const TOOLS_BY_ROLE = {
|
|
6
6
|
specialist: 'Read, Write, Edit, Bash',
|
|
7
|
-
coordinator: 'Read,
|
|
7
|
+
coordinator: 'Read, Glob, Grep, Task, Bash',
|
|
8
8
|
reviewer: 'Read, Grep, Glob, Bash',
|
|
9
9
|
architect: 'Read, Grep, Glob, Bash',
|
|
10
|
+
custom: 'Read, Bash',
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
function buildClaudeFormat(name, description, model, tools, body) {
|
|
@@ -32,9 +33,13 @@ function buildDescription(role, primaryTech, framework) {
|
|
|
32
33
|
return descriptions[role] || `Agent for ${tech}`;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
function buildSkillsFormat(name, description, body) {
|
|
37
|
+
return `---\nname: ${name}\ndescription: "${description}"\n---\n\n${body}\n`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function writeAgent({ name, role, model, tools, body, outputDir, target, description: customDesc }) {
|
|
41
|
+
const results = { claude: null, codex: null, skills: null };
|
|
42
|
+
const description = customDesc || body.split('\n').find((l) => l.trim() && !l.startsWith('#'))?.trim() || name;
|
|
38
43
|
|
|
39
44
|
const resolvedTools = tools || TOOLS_BY_ROLE[role] || 'Read, Write, Edit, Bash';
|
|
40
45
|
|
|
@@ -56,7 +61,16 @@ async function writeAgent({ name, role, model, tools, body, outputDir, target })
|
|
|
56
61
|
results.codex = codexPath;
|
|
57
62
|
}
|
|
58
63
|
|
|
64
|
+
if (target === 'codex' || target === 'all') {
|
|
65
|
+
const skillsDir = path.join(outputDir, '.agents', 'skills', name);
|
|
66
|
+
await fs.ensureDir(skillsDir);
|
|
67
|
+
const skillsContent = buildSkillsFormat(name, description, body);
|
|
68
|
+
const skillsPath = path.join(skillsDir, 'SKILL.md');
|
|
69
|
+
await fs.writeFile(skillsPath, skillsContent, 'utf8');
|
|
70
|
+
results.skills = skillsPath;
|
|
71
|
+
}
|
|
72
|
+
|
|
59
73
|
return results;
|
|
60
74
|
}
|
|
61
75
|
|
|
62
|
-
module.exports = { writeAgent, buildClaudeFormat, buildCodexFormat, buildDescription, TOOLS_BY_ROLE };
|
|
76
|
+
module.exports = { writeAgent, buildClaudeFormat, buildCodexFormat, buildSkillsFormat, buildDescription, TOOLS_BY_ROLE };
|
package/src/utils/prompts.js
CHANGED
|
@@ -6,6 +6,7 @@ const ROLES = [
|
|
|
6
6
|
{ name: 'coordinator — Orchestrates across repos', value: 'coordinator' },
|
|
7
7
|
{ name: 'reviewer — Code review and quality analysis', value: 'reviewer' },
|
|
8
8
|
{ name: 'architect — System design and technical decisions', value: 'architect' },
|
|
9
|
+
{ name: 'custom — Free-form agent with custom instructions', value: 'custom' },
|
|
9
10
|
];
|
|
10
11
|
|
|
11
12
|
const MODELS = [
|
|
@@ -21,7 +22,7 @@ const TARGETS = [
|
|
|
21
22
|
];
|
|
22
23
|
|
|
23
24
|
async function askCreateOptions() {
|
|
24
|
-
const
|
|
25
|
+
const baseQuestions = [
|
|
25
26
|
{
|
|
26
27
|
type: 'input',
|
|
27
28
|
name: 'name',
|
|
@@ -40,13 +41,39 @@ async function askCreateOptions() {
|
|
|
40
41
|
message: 'Model:',
|
|
41
42
|
choices: MODELS,
|
|
42
43
|
},
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const answers = await inquirer.prompt(baseQuestions);
|
|
47
|
+
|
|
48
|
+
if (answers.role === 'custom') {
|
|
49
|
+
const customAnswers = await inquirer.prompt([
|
|
50
|
+
{
|
|
51
|
+
type: 'input',
|
|
52
|
+
name: 'description',
|
|
53
|
+
message: 'Short description for the agent:',
|
|
54
|
+
validate: (v) => v.length >= 5 || 'Description must be at least 5 characters',
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'editor',
|
|
58
|
+
name: 'instructions',
|
|
59
|
+
message: 'Agent instructions (opens editor):',
|
|
60
|
+
},
|
|
61
|
+
]);
|
|
62
|
+
Object.assign(answers, customAnswers);
|
|
63
|
+
} else {
|
|
64
|
+
const scopeAnswers = await inquirer.prompt([
|
|
65
|
+
{
|
|
66
|
+
type: 'input',
|
|
67
|
+
name: 'scope',
|
|
68
|
+
message: 'Repository path (for stack detection, optional):',
|
|
69
|
+
default: '',
|
|
70
|
+
filter: (v) => (v ? path.resolve(v) : ''),
|
|
71
|
+
},
|
|
72
|
+
]);
|
|
73
|
+
Object.assign(answers, scopeAnswers);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const outputAnswers = await inquirer.prompt([
|
|
50
77
|
{
|
|
51
78
|
type: 'list',
|
|
52
79
|
name: 'target',
|
|
@@ -62,7 +89,7 @@ async function askCreateOptions() {
|
|
|
62
89
|
},
|
|
63
90
|
]);
|
|
64
91
|
|
|
65
|
-
return answers;
|
|
92
|
+
return { ...answers, ...outputAnswers };
|
|
66
93
|
}
|
|
67
94
|
|
|
68
95
|
async function confirmGeneration(agentName, files) {
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
# {{name}}
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Guia multi-repo del workspace. Proporciona vision global de {{N}} {{repos_word}} externos, orienta sobre que especialista usar y consolida resultados.
|
|
4
|
+
|
|
5
|
+
## Agentes especialistas disponibles
|
|
6
|
+
|
|
7
|
+
{{specialist_list}}
|
|
8
|
+
|
|
9
|
+
Cada especialista opera exclusivamente dentro de su repo asignado. Para ejecutar cambios en un repo especifico, invoca al especialista correspondiente usando `/agents` o `Task`.
|
|
4
10
|
|
|
5
11
|
## Reglas
|
|
6
12
|
|
|
7
|
-
- **Vision global**: este agente conoce la arquitectura completa del workspace.
|
|
13
|
+
- **Vision global**: este agente conoce la arquitectura completa del workspace y la relacion entre los repos.
|
|
8
14
|
- Puede **leer y buscar** en cualquier repo del workspace para analisis cross-repo.
|
|
9
|
-
- **No implementar cambios directamente** en repos externos. Indicar al usuario que invoque al especialista correspondiente.
|
|
15
|
+
- **No implementar cambios directamente** en repos externos. Indicar al usuario que invoque al especialista correspondiente, o usar la herramienta Task para delegarle la subtarea.
|
|
16
|
+
- Cuando haya subtareas independientes en repos distintos, sugerir al usuario ejecutarlas en paralelo.
|
|
10
17
|
- Siempre producir un **resumen** con:
|
|
11
18
|
- Plan de accion por repo
|
|
12
19
|
- Especialista recomendado para cada subtarea
|
|
13
20
|
- Comandos de verificacion pendientes
|
|
14
|
-
- Consolidar resultados y documentacion en `./docs
|
|
21
|
+
- Consolidar resultados y documentacion en `./docs/<tema>/`.
|
|
22
|
+
- Seguir el protocolo cross-repo definido en AGENTS.md (o CLAUDE.md): planificar primero, cambios minimos, reportar archivos tocados.
|
|
15
23
|
|
|
16
24
|
## Flujo de trabajo
|
|
17
25
|
|
|
@@ -20,3 +28,9 @@ Coordinador del workspace. Proporciona vision global, orienta sobre que especial
|
|
|
20
28
|
3. Si es solo un repo: orientar al usuario hacia el especialista correspondiente.
|
|
21
29
|
4. Si es cross-repo: planificar la secuencia, indicar el orden de invocacion de especialistas.
|
|
22
30
|
5. Consolidar documentacion y reportar al usuario.
|
|
31
|
+
|
|
32
|
+
## Agentes globales (gestionados por el usuario)
|
|
33
|
+
|
|
34
|
+
Si el usuario ha creado agentes globales/personales en el workspace (por ejemplo, agentes transversales de arquitectura, estilo o revision de codigo), puedes invocarlos para obtener analisis cross-repo. Estos agentes son gestionados directamente por el usuario y pueden no estar presentes en todos los workspaces.
|
|
35
|
+
{{skills_section}}
|
|
36
|
+
{{mcp_section}}
|