UNPKG

@andrebuzeli/advanced-memory-markdown-mcp

Version:

Advanced Memory Bank MCP v3.1.5 - Sistema avançado de gerenciamento de memória com isolamento de projetos por IDE, sincronização sob demanda, backup a cada 30min, apenas arquivos .md principais sincronizados, pasta reasoning temporária com limpeza automát

173 lines 7.08 kB
/** * Backup Manager - Sistema de backup e restore das memórias * Usa MEMORY_BACKUP_ROOT para configuração do diretório de backup */ import fs from 'fs/promises'; import { createWriteStream, existsSync, mkdirSync } from 'fs'; import path from 'path'; import archiver from 'archiver'; import extract from 'extract-zip'; export class BackupManager { topicManager; backupRoot; constructor(topicManager) { this.topicManager = topicManager; this.backupRoot = this.getBackupRoot(); this.ensureBackupRoot(); } /** * Executa ação de backup */ async executeBackupAction(action, options = {}) { switch (action) { case 'export': return this.exportBackup(options); case 'import': return this.importBackup(options); case 'restore': return this.restoreBackup(options); default: throw new Error(`Ação de backup desconhecida: ${action}`); } } /** * Obtém o diretório raiz dos backups */ getBackupRoot() { const envBackupRoot = process.env.MEMORY_BACKUP_ROOT; if (envBackupRoot) { const normalizedPath = path.resolve(envBackupRoot); process.stderr.write(`[BackupManager] Using MEMORY_BACKUP_ROOT: ${normalizedPath}\n`); return normalizedPath; } const memoryRoot = this.topicManager.getMemoryRootPath(); const backupInMemoryRoot = path.join(memoryRoot, 'backups'); process.stderr.write(`[BackupManager] MEMORY_BACKUP_ROOT not set, using: ${backupInMemoryRoot}\n`); return backupInMemoryRoot; } /** * Garante que o diretório de backup existe */ ensureBackupRoot() { try { if (!existsSync(this.backupRoot)) { mkdirSync(this.backupRoot, { recursive: true }); process.stderr.write(`[BackupManager] Created backup directory: ${this.backupRoot}\n`); } } catch (error) { process.stderr.write(`[BackupManager] Error creating backup directory: ${error}\n`); throw new Error(`Failed to create backup directory: ${this.backupRoot}`); } } generateBackupName(description) { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const slug = (description || '').toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, ''); return `backup-${timestamp}${slug ? '-' + slug : ''}`; } async exportBackup(options = {}) { const timestamp = Date.now(); const backupName = this.generateBackupName(options.description); const backupDir = path.join(this.backupRoot, backupName); await fs.mkdir(backupDir, { recursive: true }); const allProjects = await this.topicManager.listProjects(); let projectsToBackup = options.includeProjects?.length ? options.includeProjects.filter(p => allProjects.includes(p)) : allProjects; if (options.excludeProjects?.length) { projectsToBackup = projectsToBackup.filter(p => !options.excludeProjects.includes(p)); } let totalSize = 0; const memoryRoot = this.topicManager.getMemoryRootPath(); for (const project of projectsToBackup) { const projectPath = path.join(memoryRoot, project); const archivePath = path.join(backupDir, `${project}.zip`); const output = createWriteStream(archivePath); const archive = archiver('zip', { zlib: { level: 9 } }); archive.pipe(output); archive.directory(projectPath, false); await archive.finalize(); const stats = await fs.stat(archivePath); totalSize += stats.size; } const metadata = { version: '8.0.0', timestamp, description: options.description || '', projectsIncluded: projectsToBackup, backupSize: totalSize, backupPath: backupDir, compressed: true }; await fs.writeFile(path.join(backupDir, 'backup-metadata.json'), JSON.stringify(metadata, null, 2)); const summary = `Backup criado: ${projectsToBackup.length} projetos, ${this.formatBytes(totalSize)}`; return { backupPath: metadata.backupPath, metadata, summary }; } async importBackup(options = {}) { if (!options.targetDirectory) { throw new Error('targetDirectory é obrigatório para importação'); } const backupPath = path.resolve(options.targetDirectory); const imported = []; const skipped = []; const files = await fs.readdir(backupPath); const zipFiles = files.filter(f => f.endsWith('.zip')); const memoryRoot = this.topicManager.getMemoryRootPath(); for (const file of zipFiles) { const projectName = file.replace('.zip', ''); const destPath = path.join(memoryRoot, projectName); if (existsSync(destPath)) { skipped.push(projectName); continue; } const sourcePath = path.join(backupPath, file); await extract(sourcePath, { dir: destPath }); imported.push(projectName); } const summary = `Importação concluída: ${imported.length} projetos importados, ${skipped.length} ignorados`; return { imported, skipped, summary }; } async restoreBackup(options = {}) { if (!options.targetDirectory) { throw new Error('targetDirectory é obrigatório para restauração'); } const backupPath = path.resolve(options.targetDirectory); const restored = []; const files = await fs.readdir(backupPath); const zipFiles = files.filter(f => f.endsWith('.zip')); const memoryRoot = this.topicManager.getMemoryRootPath(); for (const file of zipFiles) { const projectName = file.replace('.zip', ''); const destPath = path.join(memoryRoot, projectName); if (existsSync(destPath)) { await fs.rm(destPath, { recursive: true, force: true }); } const sourcePath = path.join(backupPath, file); await extract(sourcePath, { dir: destPath }); restored.push(projectName); } const summary = `Restauração concluída: ${restored.length} projetos restaurados`; return { restored, summary }; } formatBytes(bytes) { if (bytes === 0) return '0 Bytes'; const k = 1024; const sizes = ['Bytes', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; } getBackupRootPath() { return this.backupRoot; } } //# sourceMappingURL=backup-manager.js.map