Skip to main content
Glama
fileManager.ts9.08 kB
import fs from 'fs-extra'; import path from 'path'; import { format } from 'date-fns'; import { SessionSummary, SavedSummaryFile } from './types.js'; export class FileManager { private readonly sunDir = '.sun'; constructor() { // Don't create directory in constructor to avoid blocking } /** * Ensure .sun directory exists */ private async ensureSunDirectory(): Promise<void> { try { // Get current working directory const cwd = process.cwd(); console.error(`📁 Current working directory: ${cwd}`); // Try to create .sun directory in current directory const targetDir = path.resolve(cwd, this.sunDir); await fs.ensureDir(targetDir); console.error(`✅ .sun directory created at: ${targetDir}`); } catch (error) { console.error('❌ Failed to create .sun directory in current directory:', error); // Try to create in user's home directory as fallback const homeDir = process.env.HOME || process.env.USERPROFILE; if (homeDir) { try { const fallbackSunDir = path.join(homeDir, '.sun'); await fs.ensureDir(fallbackSunDir); console.error(`✅ Created .sun directory in home directory: ${fallbackSunDir}`); // Update sunDir to use fallback (this as any).sunDir = fallbackSunDir; return; } catch (fallbackError) { console.error('❌ Failed to create .sun directory in home directory:', fallbackError); } } // Final fallback to temp directory try { const tempSunDir = path.join('/tmp', '.sun'); await fs.ensureDir(tempSunDir); console.error(`✅ Created .sun directory in temp location: ${tempSunDir}`); (this as any).sunDir = tempSunDir; } catch (tempError) { console.error('❌ Failed to create .sun directory in temp location:', tempError); throw new Error(`Cannot create .sun directory anywhere. Please check permissions.`); } } } /** * Generate filename based on current date/time and functionality */ private generateFilename(functionality: string): string { const timestamp = format(new Date(), 'yyyyMMdd_HHmmss'); const sanitizedFunc = functionality .replace(/[^a-zA-Z0-9\u4e00-\u9fa5]/g, '_') // Allow Chinese characters .replace(/_+/g, '_') .replace(/^_|_$/g, ''); return `${timestamp}_${sanitizedFunc}.mdc`; } /** * Save session summary to .mdc file */ async saveSummary(summary: SessionSummary, functionality?: string): Promise<SavedSummaryFile> { await this.ensureSunDirectory(); const func = functionality || summary.title || 'session_summary'; const filename = this.generateFilename(func); const filePath = path.join(this.sunDir, filename); // Create markdown content const content = this.formatSummaryAsMarkdown(summary); try { await fs.writeFile(filePath, content, 'utf-8'); const savedFile: SavedSummaryFile = { filename, path: filePath, summary, createdAt: new Date().toISOString() }; console.log(`Summary saved to: ${filePath}`); return savedFile; } catch (error) { console.error('Failed to save summary:', error); throw error; } } /** * Format summary as markdown content */ private formatSummaryAsMarkdown(summary: SessionSummary): string { const isEnglish = summary.language === 'en'; const content = `# ${summary.title} ## ${isEnglish ? 'Session Overview' : '会话概要'} **${isEnglish ? 'Timestamp' : '时间戳'}**: ${summary.timestamp} **${isEnglish ? 'Completion Status' : '完成状态'}**: ${summary.completionStatus} **${isEnglish ? 'Message Count' : '消息数量'}**: ${summary.messageCount} **${isEnglish ? 'Main Functionality' : '主要功能'}**: ${summary.functionality} ## ${isEnglish ? 'Core Essence' : '核心精髓'} ${summary.essence} ## ${isEnglish ? 'Key Points' : '关键要点'} ${summary.keyPoints.map(point => `- ${point}`).join('\n')} ## ${isEnglish ? 'Outcomes' : '完成成果'} ${summary.outcomes.map(outcome => `- ${outcome}`).join('\n')} ${summary.nextSteps && summary.nextSteps.length > 0 ? `## ${isEnglish ? 'Next Steps' : '后续步骤'} ${summary.nextSteps.map(step => `- ${step}`).join('\n')}` : ''} --- *Generated by Sun Session Summarizer MCP Server* *Created at: ${new Date().toISOString()}* `; return content; } /** * List all saved summary files */ async listSummaries(): Promise<SavedSummaryFile[]> { await this.ensureSunDirectory(); try { const files = await fs.readdir(this.sunDir); const mdcFiles = files.filter(file => file.endsWith('.mdc')); const summaries: SavedSummaryFile[] = []; for (const filename of mdcFiles) { const filePath = path.join(this.sunDir, filename); const stats = await fs.stat(filePath); // Try to parse the summary from the file try { const content = await fs.readFile(filePath, 'utf-8'); const summary = this.parseSummaryFromMarkdown(content, filename); summaries.push({ filename, path: filePath, summary, createdAt: stats.birthtime.toISOString() }); } catch (parseError) { console.warn(`Failed to parse summary from ${filename}:`, parseError); } } // Sort by creation time (newest first) return summaries.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() ); } catch (error) { console.error('Failed to list summaries:', error); throw error; } } /** * Parse summary from markdown content (basic implementation) */ private parseSummaryFromMarkdown(content: string, filename: string): SessionSummary { // Detect language from content const isEnglish = content.includes('Session Overview') || content.includes('Timestamp'); // Extract title (first # heading) const titleMatch = content.match(/^# (.+)$/m); const title = titleMatch ? titleMatch[1] : filename.replace('.mdc', ''); // Extract basic info (support both languages) const timestampMatch = content.match(/\*\*(Timestamp|时间戳)\*\*: (.+)$/m); const statusMatch = content.match(/\*\*(Completion Status|完成状态)\*\*: (.+)$/m); const messageCountMatch = content.match(/\*\*(Message Count|消息数量)\*\*: (\d+)$/m); // Extract essence (support both languages) const essenceMatch = content.match(/## (Core Essence|核心精髓)\n([\s\S]*?)\n\n## /) || content.match(/## (Core Essence|核心精髓)\n([\s\S]*?)$/); const essence = essenceMatch ? essenceMatch[2].trim() : ''; // Extract key points (support both languages) const keyPointsMatch = content.match(/## (Key Points|关键要点)\n([\s\S]*?)\n\n## /) || content.match(/## (Key Points|关键要点)\n([\s\S]*?)$/); const keyPoints = keyPointsMatch ? keyPointsMatch[2].split('\n').filter(line => line.startsWith('- ')).map(line => line.substring(2)) : []; // Extract outcomes (support both languages) const outcomesMatch = content.match(/## (Outcomes|完成成果)\n([\s\S]*?)(\n\n## |\n\n---)/); const outcomes = outcomesMatch ? outcomesMatch[2].split('\n').filter(line => line.startsWith('- ')).map(line => line.substring(2)) : []; return { title, essence, completionStatus: (statusMatch ? statusMatch[2] : 'unknown') as any, keyPoints, outcomes, timestamp: timestampMatch ? timestampMatch[2] : new Date().toISOString(), messageCount: messageCountMatch ? parseInt(messageCountMatch[2]) : 0, functionality: filename.split('_').slice(2).join('_').replace('.mdc', ''), language: isEnglish ? 'en' : 'zh' }; } /** * Get specific summary by filename */ async getSummary(filename: string): Promise<SavedSummaryFile | null> { const filePath = path.join(this.sunDir, filename); try { const exists = await fs.pathExists(filePath); if (!exists) { return null; } const content = await fs.readFile(filePath, 'utf-8'); const stats = await fs.stat(filePath); const summary = this.parseSummaryFromMarkdown(content, filename); return { filename, path: filePath, summary, createdAt: stats.birthtime.toISOString() }; } catch (error) { console.error(`Failed to get summary ${filename}:`, error); return null; } } /** * Delete a summary file */ async deleteSummary(filename: string): Promise<boolean> { const filePath = path.join(this.sunDir, filename); try { const exists = await fs.pathExists(filePath); if (!exists) { return false; } await fs.remove(filePath); console.log(`Summary deleted: ${filePath}`); return true; } catch (error) { console.error(`Failed to delete summary ${filename}:`, error); return false; } } }

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/ChenYCL/sun-mcp'

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