UNPKG

@gork-labs/secondbrain-mcp

Version:

Second Brain MCP Server - Agent team orchestration with dynamic tool discovery

141 lines (140 loc) 5.04 kB
import * as fs from 'fs'; import * as path from 'path'; import { fileURLToPath } from 'url'; import Mustache from 'mustache'; import { logger } from '../utils/logger.js'; /** * Template manager for loading and rendering instruction templates */ export class TemplateManager { templates = new Map(); templatesPath; initialized = false; constructor() { // Get the directory of this module const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // In development, templates are in src/templates (one level up from utils) // In production build, templates are copied to the package root const devTemplatesPath = path.join(__dirname, '../templates'); const prodTemplatesPath = path.resolve(__dirname, '../..', 'templates'); // Check which path exists if (fs.existsSync(devTemplatesPath)) { this.templatesPath = devTemplatesPath; } else if (fs.existsSync(prodTemplatesPath)) { this.templatesPath = prodTemplatesPath; } else { // Fallback to dev path for error reporting this.templatesPath = devTemplatesPath; } } /** * Initialize the template manager by loading all templates */ async initialize() { if (this.initialized) { return; } try { if (!fs.existsSync(this.templatesPath)) { throw new Error(`Templates directory not found: ${this.templatesPath}`); } const files = fs.readdirSync(this.templatesPath); const templateFiles = files.filter(file => file.endsWith('.template')); if (templateFiles.length === 0) { logger.warn('No template files found', { path: this.templatesPath }); this.initialized = true; return; } for (const file of templateFiles) { try { const filePath = path.join(this.templatesPath, file); const content = fs.readFileSync(filePath, 'utf-8'); const templateName = file.replace('.template', ''); this.templates.set(templateName, content); logger.debug('Loaded template', { name: templateName, file }); } catch (error) { logger.warn('Failed to load template file', { file, error: error instanceof Error ? error.message : String(error) }); } } this.initialized = true; logger.info('Template manager initialized', { templatesPath: this.templatesPath, loadedCount: this.templates.size }); } catch (error) { logger.error('Failed to initialize template manager', { error: error instanceof Error ? error.message : String(error) }); throw error; } } /** * Render a template with the provided variables */ render(templateName, variables = {}) { if (!this.initialized) { throw new Error('TemplateManager not initialized'); } const template = this.templates.get(templateName); if (!template) { throw new Error(`Template not found: ${templateName}`); } try { return Mustache.render(template, variables); } catch (error) { logger.error('Template rendering failed', { templateName, error: error instanceof Error ? error.message : String(error), variables: Object.keys(variables) }); throw new Error(`Failed to render template ${templateName}: ${error instanceof Error ? error.message : String(error)}`); } } /** * Check if a template exists */ hasTemplate(templateName) { return this.templates.has(templateName); } /** * Get list of available template names */ getTemplateNames() { return Array.from(this.templates.keys()).sort(); } /** * Reload templates from disk */ async reload() { this.templates.clear(); this.initialized = false; await this.initialize(); } /** * Get raw template content (for debugging) */ getRawTemplate(templateName) { return this.templates.get(templateName); } /** * Create a template renderer with pre-bound variables */ createRenderer(baseVariables = {}) { return { render: (templateName, additionalVariables = {}) => { const mergedVariables = { ...baseVariables, ...additionalVariables }; return this.render(templateName, mergedVariables); } }; } } // Export singleton instance export const templateManager = new TemplateManager();