@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
JavaScript
/**
* 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