@tacuchi/agent-factory 0.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/LICENSE ADDED
@@ -0,0 +1,37 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tacuchi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ Acknowledgments
26
+
27
+ This project was inspired in part by claude-code-templates
28
+ (https://github.com/davila7/claude-code-templates) by Daniel Ávila,
29
+ licensed under the MIT License. Specifically, the following concepts were
30
+ studied as reference:
31
+
32
+ - Stack detection patterns (detectProject utility)
33
+ - YAML frontmatter format for Claude Code agent definitions
34
+ - CLI structure using commander + inquirer + chalk + ora
35
+
36
+ All code in agent-factory is original work. No source code was copied
37
+ from claude-code-templates.
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # 🏭 agent-factory
2
+
3
+ CLI to create AI agents for **Claude Code**, **Codex**, **Gemini CLI** and more.
4
+
5
+ Analyzes your repository's tech stack and generates role-based agents ready to use.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install -g agent-factory
11
+ ```
12
+
13
+ Or run directly:
14
+
15
+ ```bash
16
+ npx agent-factory <command>
17
+ ```
18
+
19
+ ## Commands
20
+
21
+ ### `detect <path>`
22
+
23
+ Detect the technology stack of a repository.
24
+
25
+ ```bash
26
+ agent-factory detect ./my-project
27
+ agent-factory detect ./my-project --json # Pure JSON output
28
+ ```
29
+
30
+ ### `init <path>`
31
+
32
+ Analyze a repository and generate suggested agents automatically.
33
+
34
+ ```bash
35
+ agent-factory init ./my-project
36
+ agent-factory init ./my-project -y # Skip confirmations
37
+ agent-factory init ./my-project --output ./workspace -y # Write agents elsewhere
38
+ ```
39
+
40
+ ### `create`
41
+
42
+ Create a single agent interactively or with flags.
43
+
44
+ ```bash
45
+ agent-factory create
46
+ agent-factory create -n api-expert -r specialist -s ./my-project -y
47
+ ```
48
+
49
+ ### `list [path]`
50
+
51
+ List existing agents in a directory.
52
+
53
+ ```bash
54
+ agent-factory list
55
+ agent-factory list ./my-project --json # JSON output
56
+ ```
57
+
58
+ ## Global flags
59
+
60
+ | Flag | Description |
61
+ |------|-------------|
62
+ | `-q, --quiet` | Suppress all visual output (banners, spinners, logs). Useful for programmatic/SKILL usage. |
63
+ | `-V, --version` | Show version |
64
+ | `-h, --help` | Show help |
65
+
66
+ ## Supported stacks
67
+
68
+ **Languages & frameworks:** JavaScript/TypeScript (Angular, React, Vue, Next.js, Svelte, Nuxt, Node.js), Java (Spring Boot β€” Maven & Gradle), Kotlin, Python (Django, FastAPI, Flask), Go, Rust, Dart/Flutter, .NET, Ruby on Rails.
69
+
70
+ **Supplementary:** Docker, SCSS, Tailwind CSS, Bootstrap, Angular Material.
71
+
72
+ ## Agent roles
73
+
74
+ | Role | Purpose |
75
+ |------|---------|
76
+ | **specialist** | Deep knowledge of the detected stack, implements features and fixes |
77
+ | **coordinator** | Orchestrates tasks across the project, manages cross-cutting concerns |
78
+ | **reviewer** | Code review focused on quality, patterns and best practices |
79
+ | **architect** | System design, architecture decisions and technical strategy |
80
+
81
+ ## Output
82
+
83
+ Agents are written in dual format:
84
+
85
+ - `.claude/agents/<name>.md` β€” YAML frontmatter format for Claude Code
86
+ - `.agents/<name>.md` β€” Plain markdown, compatible with other AI tools
87
+
88
+ ## Programmatic usage (SKILL-friendly)
89
+
90
+ Designed to be invoked by AI agents via SKILLs:
91
+
92
+ ```bash
93
+ # Silent detection, JSON output
94
+ agent-factory detect ./repo --json
95
+
96
+ # Silent init, separate output directory
97
+ agent-factory -q init ./repo --output ./workspace -y
98
+
99
+ # List agents as JSON
100
+ agent-factory list ./workspace --json
101
+ ```
102
+
103
+ ## Project structure
104
+
105
+ ```
106
+ agent-factory/
107
+ β”œβ”€β”€ bin/agent-factory.js # CLI entry point
108
+ β”œβ”€β”€ src/
109
+ β”‚ β”œβ”€β”€ commands/
110
+ β”‚ β”‚ β”œβ”€β”€ detect.js # Stack detection command
111
+ β”‚ β”‚ β”œβ”€β”€ create.js # Interactive agent creation
112
+ β”‚ β”‚ β”œβ”€β”€ init.js # Auto-suggest and generate agents
113
+ β”‚ β”‚ └── list.js # List and validate existing agents
114
+ β”‚ β”œβ”€β”€ core/
115
+ β”‚ β”‚ β”œβ”€β”€ stack-detector.js # Technology stack analysis
116
+ β”‚ β”‚ β”œβ”€β”€ template-engine.js # Template rendering with Handlebars-like syntax
117
+ β”‚ β”‚ β”œβ”€β”€ agent-writer.js # Writes agents to dual formats
118
+ β”‚ β”‚ └── agent-validator.js # Validates agent file structure
119
+ β”‚ └── utils/
120
+ β”‚ β”œβ”€β”€ logger.js # Colored output, spinners, quiet mode
121
+ β”‚ └── prompts.js # Inquirer prompt builders
122
+ └── templates/
123
+ └── roles/ # Agent role templates
124
+ β”œβ”€β”€ specialist.md.tmpl
125
+ β”œβ”€β”€ coordinator.md.tmpl
126
+ β”œβ”€β”€ reviewer.md.tmpl
127
+ └── architect.md.tmpl
128
+ ```
129
+
130
+ ## Inspiration & acknowledgments
131
+
132
+ This project was built from scratch as an original work. The concept and architecture were inspired in part by [claude-code-templates](https://github.com/davila7/claude-code-templates) by Daniel Ávila, from which the following ideas were studied as reference:
133
+
134
+ - Stack detection patterns
135
+ - YAML frontmatter format for Claude Code agents
136
+ - CLI tooling choices (commander, inquirer, chalk, ora)
137
+
138
+ No source code was copied. All implementation is original.
139
+
140
+ ## License
141
+
142
+ [MIT](LICENSE)
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require('commander');
4
+ const { banner, setQuiet } = require('../src/utils/logger');
5
+ const pkg = require('../package.json');
6
+
7
+ program
8
+ .name('agent-factory')
9
+ .description('CLI to create AI agents for Claude Code, Codex, Gemini CLI and more')
10
+ .version(pkg.version)
11
+ .option('-q, --quiet', 'Suppress banner and visual output (for programmatic use)')
12
+ .hook('preAction', (thisCommand, actionCommand) => {
13
+ const globalOpts = thisCommand.opts();
14
+ const subOpts = actionCommand.opts();
15
+ if (globalOpts.quiet || subOpts.json) {
16
+ setQuiet(true);
17
+ } else {
18
+ banner(pkg.version);
19
+ }
20
+ });
21
+
22
+ program
23
+ .command('detect <path>')
24
+ .description('Detect technology stack of a repository')
25
+ .option('-v, --verbose', 'Show detailed detection info')
26
+ .option('--json', 'Output as JSON (implies --quiet)')
27
+ .action(async (repoPath, options) => {
28
+ const { runDetect } = require('../src/commands/detect');
29
+ await runDetect(repoPath, options);
30
+ });
31
+
32
+ program
33
+ .command('create')
34
+ .description('Create an AI agent (interactive or with flags)')
35
+ .option('-n, --name <name>', 'Agent name (kebab-case)')
36
+ .option('-r, --role <role>', 'Agent role: specialist, coordinator, reviewer, architect')
37
+ .option('-m, --model <model>', 'Model: opus, sonnet, haiku', 'sonnet')
38
+ .option('-s, --scope <path>', 'Repository path (for stack detection)')
39
+ .option('-o, --output <path>', 'Output directory (default: current dir)')
40
+ .option('-t, --target <target>', 'Target: claude, codex, all', 'all')
41
+ .option('--tools <tools>', 'Comma-separated tools: Read,Write,Edit,Bash')
42
+ .option('-y, --yes', 'Skip confirmations')
43
+ .action(async (options) => {
44
+ const { runCreate } = require('../src/commands/create');
45
+ await runCreate(options);
46
+ });
47
+
48
+ program
49
+ .command('init <path>')
50
+ .description('Analyze repository and suggest agents')
51
+ .option('-o, --output <path>', 'Output directory (default: same as repo path)')
52
+ .option('-t, --target <target>', 'Target: claude, codex, all', 'all')
53
+ .option('-m, --model <model>', 'Model for generated agents', 'sonnet')
54
+ .option('-y, --yes', 'Skip confirmations')
55
+ .action(async (repoPath, options) => {
56
+ const { runInit } = require('../src/commands/init');
57
+ await runInit(repoPath, options);
58
+ });
59
+
60
+ program
61
+ .command('list [path]')
62
+ .description('List existing agents in a directory')
63
+ .option('-v, --verbose', 'Show validation details')
64
+ .option('--json', 'Output as JSON (implies --quiet)')
65
+ .action(async (dirPath, options) => {
66
+ const { runList } = require('../src/commands/list');
67
+ await runList(dirPath || process.cwd(), options);
68
+ });
69
+
70
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@tacuchi/agent-factory",
3
+ "version": "0.1.0",
4
+ "description": "CLI to create AI agents for Claude Code, Codex, Gemini CLI and more",
5
+ "bin": {
6
+ "agent-factory": "bin/agent-factory.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "src/",
11
+ "templates/",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "engines": {
16
+ "node": ">=16"
17
+ },
18
+ "scripts": {
19
+ "test": "node --test tests/",
20
+ "start": "node bin/agent-factory.js"
21
+ },
22
+ "keywords": [
23
+ "claude-code",
24
+ "agents",
25
+ "codex",
26
+ "gemini",
27
+ "ai",
28
+ "cli",
29
+ "agent-factory"
30
+ ],
31
+ "author": "tacuchi",
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/tacuchi/agent-factory"
36
+ },
37
+ "dependencies": {
38
+ "chalk": "^4.1.2",
39
+ "commander": "^11.1.0",
40
+ "fs-extra": "^11.2.0",
41
+ "inquirer": "^8.2.6",
42
+ "js-yaml": "^4.1.0",
43
+ "ora": "^5.4.1"
44
+ }
45
+ }
@@ -0,0 +1,99 @@
1
+ const path = require('path');
2
+ const { detect } = require('../core/stack-detector');
3
+ const { renderFile } = require('../core/template-engine');
4
+ const { writeAgent, buildDescription, TOOLS_BY_ROLE } = require('../core/agent-writer');
5
+ const { AgentValidator } = require('../core/agent-validator');
6
+ const { log, spinner } = require('../utils/logger');
7
+ const { askCreateOptions, confirmGeneration } = require('../utils/prompts');
8
+
9
+ async function runCreate(options = {}) {
10
+ const isInteractive = !options.name;
11
+ const config = isInteractive ? await askCreateOptions() : normalizeFlags(options);
12
+
13
+ const { name, role, model, scope, output, target, tools } = config;
14
+
15
+ const spin = spinner('Generating agent...').start();
16
+
17
+ let stackResult = { primaryTech: 'Generic', framework: '', verifyCommands: '', stackParts: [], stackCsv: 'Generic' };
18
+ if (scope) {
19
+ stackResult = await detect(scope);
20
+ }
21
+
22
+ const templateData = {
23
+ name,
24
+ primary_tech: stackResult.primaryTech,
25
+ framework: stackResult.framework,
26
+ scope: scope || '.',
27
+ stack_list: stackResult.stackParts.length > 0
28
+ ? stackResult.stackParts.map((p) => `- ${p}`).join('\n')
29
+ : `- ${stackResult.primaryTech}`,
30
+ verify_cmds: stackResult.verifyCommands || 'N/A',
31
+ stack_csv: stackResult.stackCsv,
32
+ };
33
+
34
+ const templateFile = `${role}.md.tmpl`;
35
+ let body;
36
+ try {
37
+ body = await renderFile(templateFile, templateData);
38
+ } catch {
39
+ spin.fail(`Template not found: ${templateFile}`);
40
+ process.exit(1);
41
+ }
42
+
43
+ spin.succeed('Agent generated');
44
+
45
+ const outputDir = output || process.cwd();
46
+ const targetDirs = [];
47
+ if (target === 'claude' || target === 'all') targetDirs.push('.claude/agents/');
48
+ if (target === 'codex' || target === 'all') targetDirs.push('.agents/');
49
+
50
+ if (!options.yes && isInteractive) {
51
+ const confirmed = await confirmGeneration(name, targetDirs);
52
+ if (!confirmed) {
53
+ log.warn('Cancelled');
54
+ return;
55
+ }
56
+ }
57
+
58
+ const resolvedTools = tools || TOOLS_BY_ROLE[role] || 'Read, Write, Edit, Bash';
59
+ const results = await writeAgent({
60
+ name,
61
+ role,
62
+ model,
63
+ tools: resolvedTools,
64
+ body,
65
+ outputDir,
66
+ target,
67
+ });
68
+
69
+ const validator = new AgentValidator();
70
+ if (results.claude) {
71
+ const fs = require('fs-extra');
72
+ const content = await fs.readFile(results.claude, 'utf8');
73
+ const validation = validator.validate(content, `${name}.md`);
74
+ if (validation.valid) {
75
+ log.success(`Claude: ${results.claude} (score: ${validation.score}/100)`);
76
+ } else {
77
+ log.warn(`Claude: ${results.claude} (score: ${validation.score}/100, ${validation.errorCount} errors)`);
78
+ }
79
+ }
80
+
81
+ if (results.codex) {
82
+ log.success(`Codex: ${results.codex}`);
83
+ }
84
+ }
85
+
86
+ function normalizeFlags(options) {
87
+ return {
88
+ name: options.name,
89
+ role: options.role || 'specialist',
90
+ model: options.model || 'sonnet',
91
+ scope: options.scope ? path.resolve(options.scope) : '',
92
+ output: options.output ? path.resolve(options.output) : process.cwd(),
93
+ target: options.target || 'all',
94
+ tools: options.tools || '',
95
+ yes: options.yes || false,
96
+ };
97
+ }
98
+
99
+ module.exports = { runCreate };
@@ -0,0 +1,56 @@
1
+ const path = require('path');
2
+ const fs = require('fs-extra');
3
+ const { detect, deriveAlias } = require('../core/stack-detector');
4
+ const { log, spinner, chalk } = require('../utils/logger');
5
+
6
+ async function runDetect(repoPath, options = {}) {
7
+ const abs = path.resolve(repoPath);
8
+
9
+ if (!(await fs.pathExists(abs))) {
10
+ if (options.json) {
11
+ console.log(JSON.stringify({ error: `Path does not exist: ${abs}` }));
12
+ process.exit(1);
13
+ }
14
+ log.error(`Path does not exist: ${abs}`);
15
+ process.exit(1);
16
+ }
17
+
18
+ const spin = spinner('Detecting stack...').start();
19
+ const result = await detect(abs);
20
+ spin.succeed('Detection complete');
21
+
22
+ if (options.json) {
23
+ console.log(JSON.stringify({ alias: deriveAlias(abs), ...result }));
24
+ return result;
25
+ }
26
+
27
+ console.log('');
28
+ log.label(' Path', abs);
29
+ log.label(' Alias', deriveAlias(abs));
30
+ log.label(' Primary', result.primaryTech);
31
+
32
+ if (result.framework) {
33
+ log.label(' Framework', result.framework);
34
+ }
35
+
36
+ if (result.stackParts.length > 0) {
37
+ log.label(' Stack', result.stackCsv);
38
+ }
39
+
40
+ if (result.verifyCommands) {
41
+ log.label(' Verify', result.verifyCommands);
42
+ }
43
+
44
+ if (options.verbose && result.stackParts.length > 0) {
45
+ console.log('');
46
+ log.muted(' Stack parts:');
47
+ result.stackParts.forEach((part) => {
48
+ console.log(chalk.gray(' β€’'), part);
49
+ });
50
+ }
51
+
52
+ console.log('');
53
+ return result;
54
+ }
55
+
56
+ module.exports = { runDetect };
@@ -0,0 +1,115 @@
1
+ const path = require('path');
2
+ const fs = require('fs-extra');
3
+ const { detect, deriveAlias } = require('../core/stack-detector');
4
+ const { renderFile } = require('../core/template-engine');
5
+ const { writeAgent, TOOLS_BY_ROLE } = require('../core/agent-writer');
6
+ const { AgentValidator } = require('../core/agent-validator');
7
+ const { log, spinner, chalk } = require('../utils/logger');
8
+ const { askInitAgents } = require('../utils/prompts');
9
+
10
+ async function runInit(repoPath, options = {}) {
11
+ const abs = path.resolve(repoPath);
12
+
13
+ if (!(await fs.pathExists(abs))) {
14
+ log.error(`Path does not exist: ${abs}`);
15
+ process.exit(1);
16
+ }
17
+
18
+ const spin = spinner('Analyzing repository...').start();
19
+ const stackResult = await detect(abs);
20
+ const alias = deriveAlias(abs);
21
+ spin.succeed(`Detected: ${stackResult.primaryTech}${stackResult.framework ? ` / ${stackResult.framework}` : ''}`);
22
+
23
+ const suggestions = buildSuggestions(alias, stackResult, abs);
24
+
25
+ log.info(`Suggested agents for ${chalk.white(alias)}:`);
26
+ suggestions.forEach((s) => {
27
+ log.muted(` β€’ ${s.name} (${s.role}) β€” ${s.description}`);
28
+ });
29
+
30
+ let selected = suggestions;
31
+ if (!options.yes) {
32
+ selected = await askInitAgents(suggestions);
33
+ if (selected.length === 0) {
34
+ log.warn('No agents selected');
35
+ return;
36
+ }
37
+ }
38
+
39
+ const target = options.target || 'all';
40
+ const model = options.model || 'sonnet';
41
+ const outputDir = options.output ? path.resolve(options.output) : abs;
42
+ const validator = new AgentValidator();
43
+
44
+ for (const agent of selected) {
45
+ const templateData = {
46
+ name: agent.name,
47
+ primary_tech: stackResult.primaryTech,
48
+ framework: stackResult.framework,
49
+ scope: abs,
50
+ stack_list: stackResult.stackParts.length > 0
51
+ ? stackResult.stackParts.map((p) => `- ${p}`).join('\n')
52
+ : `- ${stackResult.primaryTech}`,
53
+ verify_cmds: stackResult.verifyCommands || 'N/A',
54
+ stack_csv: stackResult.stackCsv,
55
+ };
56
+
57
+ let body;
58
+ try {
59
+ body = await renderFile(`${agent.role}.md.tmpl`, templateData);
60
+ } catch {
61
+ log.warn(`Template not found for role: ${agent.role}, skipping`);
62
+ continue;
63
+ }
64
+
65
+ const results = await writeAgent({
66
+ name: agent.name,
67
+ role: agent.role,
68
+ model,
69
+ tools: TOOLS_BY_ROLE[agent.role],
70
+ body,
71
+ outputDir,
72
+ target,
73
+ });
74
+
75
+ if (results.claude) {
76
+ const content = await fs.readFile(results.claude, 'utf8');
77
+ const validation = validator.validate(content, `${agent.name}.md`);
78
+ log.success(`${agent.name} β†’ .claude/agents/ (score: ${validation.score}/100)`);
79
+ }
80
+ if (results.codex) {
81
+ log.success(`${agent.name} β†’ .agents/`);
82
+ }
83
+ }
84
+
85
+ log.success(`${selected.length} agent(s) generated in ${outputDir}`);
86
+ }
87
+
88
+ function buildSuggestions(alias, stackResult, repoPath) {
89
+ const suggestions = [];
90
+ const tech = stackResult.framework || stackResult.primaryTech;
91
+
92
+ suggestions.push({
93
+ name: `repo-${alias}`,
94
+ role: 'specialist',
95
+ description: `${tech} specialist for ${alias}`,
96
+ });
97
+
98
+ suggestions.push({
99
+ name: `${alias}-reviewer`,
100
+ role: 'reviewer',
101
+ description: `Code reviewer for ${tech}`,
102
+ });
103
+
104
+ if (stackResult.stackParts.length >= 3) {
105
+ suggestions.push({
106
+ name: `${alias}-architect`,
107
+ role: 'architect',
108
+ description: `Architecture advisor for ${tech}`,
109
+ });
110
+ }
111
+
112
+ return suggestions;
113
+ }
114
+
115
+ module.exports = { runInit };
@@ -0,0 +1,117 @@
1
+ const path = require('path');
2
+ const fs = require('fs-extra');
3
+ const yaml = require('js-yaml');
4
+ const { AgentValidator } = require('../core/agent-validator');
5
+ const { log, chalk } = require('../utils/logger');
6
+
7
+ async function runList(dirPath, options = {}) {
8
+ const abs = path.resolve(dirPath);
9
+ const agents = [];
10
+
11
+ const claudeDir = path.join(abs, '.claude', 'agents');
12
+ const codexDir = path.join(abs, '.agents');
13
+
14
+ if (await fs.pathExists(claudeDir)) {
15
+ const files = (await fs.readdir(claudeDir)).filter((f) => f.endsWith('.md'));
16
+ for (const file of files) {
17
+ const content = await fs.readFile(path.join(claudeDir, file), 'utf8');
18
+ agents.push({ file, source: '.claude/agents', content, format: 'claude' });
19
+ }
20
+ }
21
+
22
+ if (await fs.pathExists(codexDir)) {
23
+ const files = (await fs.readdir(codexDir)).filter((f) => f.endsWith('.md'));
24
+ for (const file of files) {
25
+ const existing = agents.find((a) => a.file === file);
26
+ if (existing) {
27
+ existing.codexToo = true;
28
+ continue;
29
+ }
30
+ const content = await fs.readFile(path.join(codexDir, file), 'utf8');
31
+ agents.push({ file, source: '.agents', content, format: 'codex' });
32
+ }
33
+ }
34
+
35
+ if (agents.length === 0) {
36
+ if (options.json) {
37
+ console.log(JSON.stringify([]));
38
+ return;
39
+ }
40
+ log.warn(`No agents found in ${abs}`);
41
+ log.muted(' Run "agent-factory init <path>" or "agent-factory create" to generate agents.');
42
+ return;
43
+ }
44
+
45
+ const validator = new AgentValidator();
46
+
47
+ if (options.json) {
48
+ const jsonResult = agents.map((agent) => {
49
+ const fm = extractFrontmatter(agent.content);
50
+ const validation = validator.validate(agent.content, agent.file);
51
+ return {
52
+ name: fm?.name || agent.file.replace('.md', ''),
53
+ model: fm?.model || null,
54
+ source: agent.codexToo ? 'both' : agent.source,
55
+ score: validation.score,
56
+ valid: validation.valid,
57
+ };
58
+ });
59
+ console.log(JSON.stringify(jsonResult));
60
+ return;
61
+ }
62
+
63
+ console.log('');
64
+ log.info(`${agents.length} agent(s) found in ${chalk.white(abs)}`);
65
+ console.log('');
66
+
67
+ const header = padRow('Name', 'Model', 'Source', 'Score');
68
+ console.log(chalk.gray(header));
69
+ console.log(chalk.gray('─'.repeat(header.length)));
70
+
71
+ for (const agent of agents) {
72
+ const fm = extractFrontmatter(agent.content);
73
+ const name = fm?.name || agent.file.replace('.md', '');
74
+ const model = fm?.model || 'β€”';
75
+ const source = agent.codexToo ? 'both' : agent.source;
76
+
77
+ const validation = validator.validate(agent.content, agent.file);
78
+ const scoreStr = `${validation.score}/100`;
79
+ const scoreColor = validation.score >= 90 ? '#10B981' : validation.score >= 70 ? '#F59E0B' : '#EF4444';
80
+
81
+ console.log(
82
+ padRow(
83
+ chalk.white(name),
84
+ chalk.gray(model),
85
+ chalk.gray(source),
86
+ chalk.hex(scoreColor)(scoreStr)
87
+ )
88
+ );
89
+
90
+ if (options.verbose) {
91
+ if (validation.errorCount > 0) {
92
+ validation.errors.forEach((e) => console.log(chalk.red(` βœ— ${e.message}`)));
93
+ }
94
+ if (validation.warningCount > 0) {
95
+ validation.warnings.forEach((w) => console.log(chalk.yellow(` ⚠ ${w.message}`)));
96
+ }
97
+ }
98
+ }
99
+
100
+ console.log('');
101
+ }
102
+
103
+ function padRow(name, model, source, score) {
104
+ return ` ${name.padEnd(30)} ${model.padEnd(10)} ${source.padEnd(18)} ${score}`;
105
+ }
106
+
107
+ function extractFrontmatter(content) {
108
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
109
+ if (!match) return null;
110
+ try {
111
+ return yaml.load(match[1]);
112
+ } catch {
113
+ return null;
114
+ }
115
+ }
116
+
117
+ module.exports = { runList };