erosolar-cli
Version:
Unified AI agent framework for the command line - Multi-provider support with schema-driven tools, code intelligence, and transparent reasoning
162 lines • 5.8 kB
JavaScript
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
import { join, basename } from 'node:path';
import { homedir } from 'node:os';
import { parseFrontmatter } from '../utils/frontmatter.js';
import { getInlineAgentConfig } from './agentConfig.js';
const PROJECT_AGENT_DIR = '.claude/agents';
const USER_AGENT_DIR = '.claude/agents';
export class AgentRegistry {
workingDir;
inlineAgents;
builtIns;
agents = new Map();
constructor(options) {
this.workingDir = options.workingDir;
this.inlineAgents = options.inlineAgents ?? getInlineAgentConfig();
this.builtIns = options.builtIns ?? [];
this.refresh();
}
refresh() {
this.agents.clear();
// Lowest priority: built-in agents
for (const agent of this.builtIns) {
this.register(agent);
}
// User-level agents (~/.claude/agents)
const userDir = join(homedir(), USER_AGENT_DIR);
this.loadFromDirectory(userDir, 'user');
// Inline agents (CLI/env)
if (this.inlineAgents) {
this.loadInlineAgents(this.inlineAgents);
}
// Project-level agents (.claude/agents)
const projectDir = join(this.workingDir, PROJECT_AGENT_DIR);
this.loadFromDirectory(projectDir, 'project');
return this.list();
}
list() {
return Array.from(this.agents.values()).sort((a, b) => a.name.localeCompare(b.name));
}
resolve(name) {
const key = normalizeName(name);
return this.agents.get(key) ?? null;
}
register(agent) {
const key = normalizeName(agent.name);
if (!key) {
return;
}
this.agents.set(key, {
...agent,
name: key,
});
}
loadFromDirectory(dir, source) {
if (!existsSync(dir)) {
return;
}
let entries = [];
try {
entries = readdirSync(dir);
}
catch {
return;
}
for (const entry of entries) {
const fullPath = join(dir, entry);
try {
const stats = statSync(fullPath);
if (stats.isDirectory()) {
this.loadFromDirectory(fullPath, source);
continue;
}
if (!entry.toLowerCase().endsWith('.md')) {
continue;
}
const content = readFileSync(fullPath, 'utf8');
const parsed = parseFrontmatter(content);
const name = normalizeName(parsed.attributes.name || basename(entry, '.md'));
if (!name) {
continue;
}
const description = extractDescription(parsed);
const prompt = parsed.body?.trim() || '';
this.register({
name,
description,
prompt,
tools: normalizeStringList(parsed.attributes.tools),
model: parsed.attributes.model ? String(parsed.attributes.model) : undefined,
permissionMode: parsed.attributes.permissionMode ? String(parsed.attributes.permissionMode) : undefined,
skills: normalizeStringList(parsed.attributes.skills),
summary: parsed.attributes.summary ? String(parsed.attributes.summary) : undefined,
source,
path: fullPath,
});
}
catch (error) {
console.warn(`[agent-registry] Failed to load agent from ${fullPath}:`, error);
}
}
}
loadInlineAgents(definitions) {
for (const [name, config] of Object.entries(definitions)) {
const normalized = normalizeName(name);
if (!normalized) {
continue;
}
this.register({
name: normalized,
description: config.description || `Inline agent ${normalized}`,
prompt: config.prompt || '',
tools: Array.isArray(config.tools) ? config.tools : config.tools ? [config.tools] : undefined,
model: config.model,
permissionMode: config.permissionMode,
skills: Array.isArray(config.skills) ? config.skills : config.skills ? [config.skills] : undefined,
source: 'inline',
});
}
}
}
function normalizeName(value) {
if (typeof value !== 'string') {
return '';
}
const trimmed = value.trim();
if (!trimmed) {
return '';
}
return trimmed.toLowerCase().replace(/\s+/g, '-');
}
function normalizeStringList(value) {
if (!value) {
return undefined;
}
if (typeof value === 'string') {
const trimmed = value.trim();
if (!trimmed) {
return undefined;
}
return trimmed.split(',').map((entry) => entry.trim()).filter(Boolean);
}
if (Array.isArray(value)) {
const normalized = value
.filter((entry) => typeof entry === 'string')
.map((entry) => entry.trim())
.filter(Boolean);
return normalized.length ? normalized : undefined;
}
return undefined;
}
function extractDescription(parsed) {
const attrDescription = typeof parsed.attributes['description'] === 'string' ? parsed.attributes['description'].trim() : '';
if (attrDescription) {
return attrDescription;
}
const firstLine = parsed.body?.split('\n').find((line) => line.trim().length > 0);
if (firstLine) {
return firstLine.trim().slice(0, 200);
}
return 'Custom subagent';
}
//# sourceMappingURL=agentRegistry.js.map