UNPKG

codemesh

Version:

Execute TypeScript code against multiple MCP servers, weaving them together into powerful workflows

188 lines (187 loc) 7.37 kB
import { appendFileSync, mkdirSync, existsSync, readFileSync } from 'node:fs'; import { join, dirname } from 'node:path'; export class FileLogger { static instance = null; config = null; currentLogFile = null; sessionStartTime = null; constructor() { } static getInstance() { if (!FileLogger.instance) { FileLogger.instance = new FileLogger(); } return FileLogger.instance; } configure(config) { this.config = config || null; if (this.config?.enabled) { this.initSession(); } } isEnabled() { return this.config?.enabled === true; } getVersion() { try { // Read package.json from the codemesh-server directory const packageJsonPath = join(dirname(new URL(import.meta.url).pathname), '..', 'package.json'); const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); return packageJson.version || 'unknown'; } catch (error) { console.error('Failed to read package.json version:', error); return 'unknown'; } } initSession() { if (!this.config?.enabled) return; const now = new Date(); this.sessionStartTime = now; const dateStr = now.toISOString().split('T')[0]; // YYYY-MM-DD const logDir = this.config.logDir || '.codemesh/logs'; const version = this.getVersion(); // Create log directory if it doesn't exist if (!existsSync(logDir)) { mkdirSync(logDir, { recursive: true }); } this.currentLogFile = join(logDir, `${dateStr}.md`); // Write session header if this is a new file if (!existsSync(this.currentLogFile)) { this.writeToFile(`# CodeMesh Session Log\n`); this.writeToFile(`**Date:** ${dateStr}\n`); this.writeToFile(`**Version:** ${version}\n\n`); this.writeToFile(`---\n\n`); } else { // Add separator for new session in existing file this.writeToFile(`\n---\n\n`); this.writeToFile(`## New Session - ${now.toTimeString().split(' ')[0]}\n`); this.writeToFile(`**Version:** ${version}\n\n`); } } writeToFile(content) { if (!this.currentLogFile) return; try { appendFileSync(this.currentLogFile, content, 'utf-8'); } catch (error) { // Silently fail to avoid disrupting tool execution console.error('Failed to write to log file:', error); } } formatTimestamp() { return new Date().toTimeString().split(' ')[0]; // HH:MM:SS } formatDuration(ms) { if (ms < 1000) return `${ms}ms`; return `${(ms / 1000).toFixed(1)}s`; } getStatusEmoji(status) { return status === 'success' ? '✅' : '❌'; } logToolCall(log) { if (!this.config?.enabled) return; const timestamp = this.formatTimestamp(); const duration = this.formatDuration(log.duration); const statusEmoji = this.getStatusEmoji(log.status); const exploringTag = log.isExploring ? ' (EXPLORING)' : ''; let content = `## ${timestamp} - ${log.tool}${exploringTag}\n`; content += `**Duration:** ${duration} \n`; content += `**Status:** ${statusEmoji} ${log.status === 'success' ? 'Success' : 'Error'}\n\n`; // Log arguments if present if (log.args && Object.keys(log.args).length > 0) { content += `### Request\n`; if (log.tool === 'execute-code' && log.code) { content += `\`\`\`typescript\n${log.code}\n\`\`\`\n\n`; } else if (log.tool === 'add-augmentation' && log.args.toolName) { content += `**Tool Name:** ${log.args.toolName}\n\n`; if (log.args.markdown) { content += `**Markdown:**\n\n---\n\n${log.args.markdown}\n\n---\n\n`; } if (log.args.codemeshDir) { content += `**Directory:** ${log.args.codemeshDir}\n\n`; } } else { content += `\`\`\`json\n${JSON.stringify(log.args, null, 2)}\n\`\`\`\n\n`; } } else { content += `### Request\nNo arguments\n\n`; } // Log console output if present if (log.consoleOutput) { content += `### Console Output\n`; content += `\`\`\`\n${log.consoleOutput}\n\`\`\`\n\n`; } // Log response content += `### Response\n`; if (log.status === 'error' && log.error) { content += `\`\`\`\n${log.error}\n\`\`\`\n\n`; } else if (log.response) { // Try to format response nicely if (typeof log.response === 'string') { // Check if it looks like code or JSON if (log.response.trim().startsWith('{') || log.response.trim().startsWith('[')) { try { const parsed = JSON.parse(log.response); content += `\`\`\`json\n${JSON.stringify(parsed, null, 2)}\n\`\`\`\n\n`; } catch { content += `\`\`\`\n${log.response}\n\`\`\`\n\n`; } } else { content += `\n${log.response}\n\n`; } } else { content += `\`\`\`json\n${JSON.stringify(log.response, null, 2)}\n\`\`\`\n\n`; } } content += `---\n\n`; this.writeToFile(content); } logMcpResponse(tool, result, duration, isExploring) { if (!this.config?.enabled) return; const timestamp = this.formatTimestamp(); const durationStr = this.formatDuration(duration); const exploringTag = isExploring ? ' (EXPLORING)' : ''; const statusEmoji = result.isError ? '❌' : '✅'; const statusText = result.isError ? 'Error' : 'Success'; let content = `### 📤 MCP Response - ${timestamp} - ${tool}${exploringTag}\n`; content += `**Duration:** ${durationStr} \n`; content += `**Status:** ${statusEmoji} ${statusText} \n`; content += `**isError:** ${result.isError || false}\n\n`; content += `#### Complete Response Object\n`; content += `\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\`\n\n`; content += `---\n\n`; this.writeToFile(content); } logMessage(level, message) { if (!this.config?.enabled) return; const levels = ['debug', 'info', 'warn', 'error']; const configLevel = this.config.level || 'info'; // Check if message should be logged based on level if (levels.indexOf(level) < levels.indexOf(configLevel)) { return; } const timestamp = this.formatTimestamp(); const emoji = { debug: '🔍', info: 'ℹ️', warn: '⚠️', error: '❌', }[level]; const content = `${timestamp} ${emoji} **${level.toUpperCase()}**: ${message}\n\n`; this.writeToFile(content); } }