claudekit
Version:
CLI tools for Claude Code development workflow
195 lines (173 loc) • 5.34 kB
text/typescript
// cli/lib/agents/registry-grouping.ts
// Agent grouping system that works with the component registry
import type { ComponentRegistry } from '../components.js';
// Agent component interface that works with registry
export interface AgentComponent {
id: string;
name: string;
description: string;
category: string | undefined;
bundle: string[] | undefined;
displayName: string | undefined;
}
export interface AgentCategoryGroup {
category: string;
title: string;
description: string;
agents: AgentComponent[];
preSelected?: boolean;
}
// Define the agent categories with their display information
export const AGENT_CATEGORIES = [
{
category: 'general',
title: 'General Purpose',
description: 'General development assistants and tools',
preSelected: true,
},
{
category: 'framework',
title: 'Frameworks & Core',
description: 'Core frameworks and language support',
preSelected: false,
},
{
category: 'testing',
title: 'Testing',
description: 'Testing frameworks and tools',
preSelected: false,
},
{
category: 'linting',
title: 'Linting & Formatting',
description: 'Code linting, formatting, and style enforcement',
preSelected: false,
},
{
category: 'database',
title: 'Database',
description: 'Database systems and ORMs',
preSelected: false,
},
{
category: 'build',
title: 'Build Tools',
description: 'Build and bundling tools',
preSelected: false,
},
{
category: 'frontend',
title: 'Frontend',
description: 'Frontend development tools',
preSelected: false,
},
{
category: 'devops',
title: 'DevOps',
description: 'Infrastructure and deployment tools',
preSelected: false,
},
{
category: 'tools',
title: 'Development Tools',
description: 'Additional development and analysis tools',
preSelected: false,
},
];
/**
* Convert registry component to agent component with metadata
*/
function toAgentComponent(component: unknown): AgentComponent {
// Cast to any to access nested properties, then validate
const comp = component as {
metadata: {
id: string;
name: string;
description: string;
[key: string]: unknown;
};
};
const metadata = comp.metadata;
return {
id: metadata.id,
name: metadata.name,
description: metadata.description,
category: metadata['agentCategory'] as string | undefined,
bundle: metadata['bundle'] as string[] | undefined,
displayName: metadata['displayName'] as string | undefined,
};
}
/**
* Get display name for an agent, handling bundles
*/
export function getAgentDisplayName(agent: AgentComponent): string {
let displayName = agent.displayName ?? agent.name;
// If this agent bundles other agents, indicate how many
if (agent.bundle !== undefined && agent.bundle.length > 0) {
displayName += ` (${agent.bundle.length + 1} agents)`;
}
return displayName;
}
/**
* Group agents from the registry based on their category
*/
export function groupAgentsByCategory(registry: ComponentRegistry): AgentCategoryGroup[] {
const agentComponents = Array.from(registry.components.values())
.filter((c) => c.type === 'agent')
.map(toAgentComponent);
// Build a set of all bundled agent IDs (agents that are included in bundles)
const bundledAgentIds = new Set<string>();
agentComponents.forEach((agent) => {
if (agent.bundle && Array.isArray(agent.bundle)) {
agent.bundle.forEach((bundledId: string) => bundledAgentIds.add(bundledId));
}
});
// Filter out bundled agents from the main list
const visibleAgents = agentComponents.filter((agent) => !bundledAgentIds.has(agent.id));
// Group agents by category
const groups: AgentCategoryGroup[] = [];
for (const categoryDef of AGENT_CATEGORIES) {
const categoryAgents = visibleAgents.filter((agent) => agent.category === categoryDef.category);
if (categoryAgents.length > 0) {
groups.push({
...categoryDef,
agents: categoryAgents.sort((a, b) => a.name.localeCompare(b.name)),
});
}
}
// Add a group for uncategorized agents (for debugging/migration)
const uncategorized = visibleAgents.filter(
(agent) => agent.category === undefined || agent.category === ''
);
if (uncategorized.length > 0) {
groups.push({
category: 'uncategorized',
title: 'Other Agents',
description: 'Agents without categories',
agents: uncategorized,
preSelected: false,
});
}
return groups;
}
/**
* Calculate final list of agents to install based on selections
*/
export function calculateSelectedAgents(
registry: ComponentRegistry,
selectedAgentIds: string[]
): string[] {
const finalAgents = new Set<string>(selectedAgentIds);
// Get all agent components
const agentComponents = Array.from(registry.components.values())
.filter((c) => c.type === 'agent')
.map(toAgentComponent);
// Add bundled agents for any selected agents that have bundles
selectedAgentIds.forEach((agentId) => {
const agent = agentComponents.find((a) => a.id === agentId);
if (agent && agent.bundle && Array.isArray(agent.bundle)) {
agent.bundle.forEach((bundledId) => finalAgents.add(bundledId));
}
});
return Array.from(finalAgents);
}