// AGENT INVENTORY SCANNER - Runs BEFORE building any new agent
// Creates a "shopping list" of available tools and agents to reuse
// Prevents duplicate work and identifies reusable components
import { promises as fs } from 'fs';
import path from 'path';
export class AgentInventoryScanner {
constructor() {
this.inventoryPath = 'C:/MCP/agent-inventory.json';
this.scanPaths = [
'C:/MCP/src/agents',
'C:/AI Projects/agents',
'C:/AI Projects/scripts',
'C:/tools',
'C:/AgentSystem'
];
this.inventory = {
lastScanned: null,
totalAgents: 0,
totalTools: 0,
agents: {},
tools: {},
workflows: {},
reusableComponents: []
};
}
// MAIN METHOD: Run full scan and create shopping list
async scan() {
console.log('🔍 Starting Agent Inventory Scan...');
try {
// Scan all directories
for (const scanPath of this.scanPaths) {
await this.scanDirectory(scanPath);
}
// Generate reusable components list
this.identifyReusableComponents();
// Save inventory
await this.saveInventory();
console.log('✅ Agent Inventory Scan Complete!');
console.log(`📊 Found: ${this.inventory.totalAgents} agents, ${this.inventory.totalTools} tools`);
return this.inventory;
} catch (error) {
console.error('❌ Scan failed:', error);
throw error;
}
}
// Scan a directory for agents and tools
async scanDirectory(dirPath) {
try {
const exists = await this.directoryExists(dirPath);
if (!exists) {
console.log(`⚠️ Directory not found: ${dirPath}`);
return;
}
const entries = await fs.readdir(dirPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
// Skip node_modules and hidden folders
if (entry.name === 'node_modules' || entry.name.startsWith('.')) {
continue;
}
await this.scanDirectory(fullPath);
} else if (entry.isFile()) {
await this.analyzeFile(fullPath, entry.name);
}
}
} catch (error) {
console.error(`⚠️ Failed to scan ${dirPath}:`, error.message);
}
}
// Analyze a file to determine if it's an agent or tool
async analyzeFile(filePath, fileName) {
try {
// Only analyze JS/TS files
if (!fileName.endsWith('.js') && !fileName.endsWith('.ts')) {
return;
}
const content = await fs.readFile(filePath, 'utf-8');
// Detect if it's an agent
if (this.isAgent(content, fileName)) {
const agentInfo = this.extractAgentInfo(content, fileName, filePath);
this.inventory.agents[agentInfo.id] = agentInfo;
this.inventory.totalAgents++;
console.log(` 📌 Found Agent: ${agentInfo.name}`);
}
// Detect if it's a tool
else if (this.isTool(content, fileName)) {
const toolInfo = this.extractToolInfo(content, fileName, filePath);
this.inventory.tools[toolInfo.id] = toolInfo;
this.inventory.totalTools++;
console.log(` 🔧 Found Tool: ${toolInfo.name}`);
}
} catch (error) {
// Skip files that can't be read
}
}
// Check if file is an agent
isAgent(content, fileName) {
const agentIndicators = [
'export class',
'class Agent',
'Agent extends',
'agent-',
'orchestrator',
'controller'
];
return agentIndicators.some(indicator =>
content.includes(indicator) || fileName.toLowerCase().includes(indicator)
);
}
// Check if file is a tool
isTool(content, fileName) {
const toolIndicators = [
'export function',
'export const',
'-tools',
'-client',
'integration'
];
return toolIndicators.some(indicator =>
content.includes(indicator) || fileName.toLowerCase().includes(indicator)
);
}
// Extract agent information
extractAgentInfo(content, fileName, filePath) {
const name = this.extractClassName(content) || fileName.replace(/\.(js|ts)$/, '');
const id = this.generateId(name);
return {
id: id,
name: name,
type: 'agent',
filePath: filePath,
fileName: fileName,
capabilities: this.extractCapabilities(content),
dependencies: this.extractDependencies(content),
hasMCPIntegration: content.includes('mcp') || content.includes('MCP'),
hasNotionSync: content.includes('notion'),
hasDatabase: content.includes('pool.query') || content.includes('database'),
scannedAt: new Date().toISOString()
};
}
// Extract tool information
extractToolInfo(content, fileName, filePath) {
const name = this.extractExportName(content) || fileName.replace(/\.(js|ts)$/, '');
const id = this.generateId(name);
return {
id: id,
name: name,
type: 'tool',
filePath: filePath,
fileName: fileName,
functions: this.extractFunctions(content),
dependencies: this.extractDependencies(content),
scannedAt: new Date().toISOString()
};
}
// Extract class name from code
extractClassName(content) {
const classMatch = content.match(/export class (\w+)/);
return classMatch ? classMatch[1] : null;
}
// Extract export name from code
extractExportName(content) {
const exportMatch = content.match(/export (?:const|function) (\w+)/);
return exportMatch ? exportMatch[1] : null;
}
// Extract capabilities from agent code
extractCapabilities(content) {
const capabilities = [];
const capabilityPatterns = {
'Document Control': /doc.*control|documentation|version/i,
'File Organization': /organiz|filing|directory/i,
'Quality Review': /quality|review|assess/i,
'Database Management': /database|query|pool/i,
'Notion Sync': /notion.*sync|syncToNotion/i,
'Workflow Orchestration': /orchestrat|workflow|pipeline/i,
'Approval System': /approval|review.*board/i
};
for (const [capability, pattern] of Object.entries(capabilityPatterns)) {
if (pattern.test(content)) {
capabilities.push(capability);
}
}
return capabilities;
}
// Extract function names from code
extractFunctions(content) {
const functions = [];
const functionMatches = content.matchAll(/(?:async\s+)?(?:function\s+)?(\w+)\s*\([^)]*\)\s*{/g);
for (const match of functionMatches) {
functions.push(match[1]);
}
return functions.slice(0, 10); // Limit to first 10
}
// Extract dependencies from imports
extractDependencies(content) {
const deps = [];
const importMatches = content.matchAll(/import .* from ['"](.+)['"]/g);
for (const match of importMatches) {
deps.push(match[1]);
}
return [...new Set(deps)]; // Remove duplicates
}
// Identify reusable components across agents
identifyReusableComponents() {
const components = [];
// Find core capabilities that appear in multiple agents
const capabilityMap = {};
for (const agent of Object.values(this.inventory.agents)) {
for (const capability of agent.capabilities) {
if (!capabilityMap[capability]) {
capabilityMap[capability] = [];
}
capabilityMap[capability].push(agent.name);
}
}
// Mark capabilities used by multiple agents as reusable
for (const [capability, agents] of Object.entries(capabilityMap)) {
if (agents.length >= 2) {
components.push({
capability: capability,
usedBy: agents,
recommendation: 'Core component - use in new agents'
});
}
}
this.inventory.reusableComponents = components;
}
// Save inventory to JSON file
async saveInventory() {
this.inventory.lastScanned = new Date().toISOString();
await fs.writeFile(
this.inventoryPath,
JSON.stringify(this.inventory, null, 2),
'utf-8'
);
console.log(`💾 Inventory saved to: ${this.inventoryPath}`);
}
// Helper: Check if directory exists
async directoryExists(dirPath) {
try {
await fs.access(dirPath);
return true;
} catch {
return false;
}
}
// Helper: Generate unique ID
generateId(name) {
return name.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
}
// QUERY METHOD: Search for existing agents/tools before building
async findSimilar(description) {
// Load inventory if not loaded
if (this.inventory.totalAgents === 0) {
await this.loadInventory();
}
const keywords = description.toLowerCase().split(/\s+/);
const matches = [];
// Search agents
for (const agent of Object.values(this.inventory.agents)) {
let score = 0;
const searchText = `${agent.name} ${agent.capabilities.join(' ')}`.toLowerCase();
for (const keyword of keywords) {
if (searchText.includes(keyword)) {
score++;
}
}
if (score > 0) {
matches.push({ ...agent, matchScore: score, category: 'agent' });
}
}
// Search tools
for (const tool of Object.values(this.inventory.tools)) {
let score = 0;
const searchText = `${tool.name} ${tool.functions.join(' ')}`.toLowerCase();
for (const keyword of keywords) {
if (searchText.includes(keyword)) {
score++;
}
}
if (score > 0) {
matches.push({ ...tool, matchScore: score, category: 'tool' });
}
}
// Sort by match score
matches.sort((a, b) => b.matchScore - a.matchScore);
return matches.slice(0, 5); // Return top 5 matches
}
// Load existing inventory
async loadInventory() {
try {
const data = await fs.readFile(this.inventoryPath, 'utf-8');
this.inventory = JSON.parse(data);
console.log('📂 Loaded existing inventory');
} catch (error) {
console.log('📂 No existing inventory found - will create new');
}
}
}
// Export singleton instance
export const agentInventory = new AgentInventoryScanner();