Skip to main content
Glama
function-registry.ts5.43 kB
import { readdirSync, readFileSync } from 'fs'; import { join, extname, basename, dirname } from 'path'; import { fileURLToPath } from 'url'; // ES module __dirname equivalent const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); export interface FunctionInfo { name: string; category: string; file: string; description?: string; supportedModes: string[]; primaryMode: string; exampleUsage?: { singleFile?: string; multiFile?: string; }; } /** * Discover all available Houtini LM functions by scanning prompt directories */ export async function discoverAvailableFunctions(): Promise<FunctionInfo[]> { const functions: FunctionInfo[] = []; const promptsDir = join(__dirname, '../prompts'); // Known prompt categories const categories = ['analyze', 'generate', 'system', 'custom', 'fun']; for (const category of categories) { const categoryPath = join(promptsDir, category); try { const files = readdirSync(categoryPath); for (const file of files) { if (file.endsWith('.js') && !file.includes('legacy') && !file.includes('.d.ts')) { try { const functionInfo = await extractFunctionInfo(categoryPath, file, category); if (functionInfo) { functions.push(functionInfo); } } catch (error) { // Skip files that can't be analyzed console.warn(`Could not analyze ${file}:`, error.message); } } } } catch (error) { // Category directory doesn't exist or can't be read, skip console.warn(`Could not read category ${category}:`, error.message); } } return functions.sort((a, b) => a.name.localeCompare(b.name)); } /** * Extract function information from a compiled plugin file */ async function extractFunctionInfo(categoryPath: string, file: string, category: string): Promise<FunctionInfo | null> { const filePath = join(categoryPath, file); try { const content = readFileSync(filePath, 'utf8'); // Extract function name (look for name = 'function_name' pattern) const nameMatch = content.match(/name\s*=\s*['"']([^'"]+)['"']/); const functionName = nameMatch ? nameMatch[1] : basename(file, '.js').replace(/-/g, '_'); // Extract description (look for description = 'text' pattern) const descMatch = content.match(/description\s*=\s*['"']([^'"]+)['"']/); const description = descMatch ? descMatch[1] : `${category} function`; // Detect supported modes by looking for mode detection logic const supportedModes = detectSupportedModes(content); const primaryMode = determinePrimaryMode(content, category); // Generate example usage const exampleUsage = generateExampleUsage(functionName, supportedModes); return { name: functionName, category, file, description, supportedModes, primaryMode, exampleUsage }; } catch (error) { console.warn(`Error reading ${file}:`, error.message); return null; } } /** * Detect which modes a function supports by analyzing its code */ function detectSupportedModes(content: string): string[] { const modes: string[] = []; // Check for single-file support if (content.includes('getSingleFile') || content.includes("'single-file'") || content.includes('params.code') || content.includes('params.filePath')) { modes.push('single-file'); } // Check for multi-file support if (content.includes('getMultiFile') || content.includes("'multi-file'") || content.includes('params.projectPath') || content.includes('params.files')) { modes.push('multi-file'); } // Default if we can't detect if (modes.length === 0) { modes.push('single-file'); // Conservative default } return modes; } /** * Determine the primary mode based on function purpose and defaults */ function determinePrimaryMode(content: string, category: string): string { // Look for explicit default return in detectAnalysisMode const defaultMatch = content.match(/return\s+['"'](single-file|multi-file)['"'];?\s*$/m); if (defaultMatch) { return defaultMatch[1]; } // Category-based heuristics if (category === 'system') { return 'multi-file'; // System functions typically work on projects } if (category === 'generate') { return 'single-file'; // Generation typically creates single items } // Default fallback return 'single-file'; } /** * Generate helpful example usage for different modes */ function generateExampleUsage(functionName: string, supportedModes: string[]): { singleFile?: string; multiFile?: string } { const examples: any = {}; if (supportedModes.includes('single-file')) { examples.singleFile = `houtini-lm:${functionName} filePath="C:/project/src/file.ts"`; } if (supportedModes.includes('multi-file')) { examples.multiFile = `houtini-lm:${functionName} projectPath="C:/project/src"`; } return examples; } /** * Get function categories with counts */ export async function getFunctionCategories(): Promise<{ [category: string]: number }> { const functions = await discoverAvailableFunctions(); const categories: { [category: string]: number } = {}; functions.forEach(func => { categories[func.category] = (categories[func.category] || 0) + 1; }); return categories; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/houtini-ai/lm'

If you have feedback or need assistance with the MCP directory API, please join our Discord server