UNPKG

@z-test/memory-bank-mcp

Version:
240 lines 8.38 kB
import SftpClient from 'ssh2-sftp-client'; import { logger } from '../../utils/LogManager.js'; import { join } from 'path'; export class SftpStorageProvider { constructor(config) { this.connected = false; this.client = new SftpClient(); this.config = config; } async initialize(_config) { try { // Initialize SFTP client this.client = new SftpClient(); // Connect to SFTP server await this.client.connect({ host: this.config.host, port: this.config.port, username: this.config.username, password: this.config.password, privateKey: this.config.privateKey }); logger.info('SftpStorageProvider', 'Successfully connected to SFTP server'); } catch (error) { logger.error('SftpStorageProvider', `Error initializing SFTP client: ${error}`); throw error; } } async connect() { try { await this.client.connect({ host: this.config.host, port: this.config.port, username: this.config.username, ...(this.config.password ? { password: this.config.password } : {}), ...(this.config.privateKey ? { privateKey: this.config.privateKey } : {}) }); this.connected = true; logger.debug('SftpStorageProvider', 'Connected to SFTP server'); } catch (error) { logger.error('SftpStorageProvider', `Failed to connect to SFTP server: ${error}`); throw error; } } async ensureConnected() { if (!this.connected) { await this.connect(); } } async exists(path) { await this.ensureConnected(); try { const fullPath = this.getFullPath(path); const stats = await this.client.stat(fullPath); return stats !== null; } catch (error) { return false; } } async createDirectory(path) { await this.ensureConnected(); try { const fullPath = this.getFullPath(path); await this.client.mkdir(fullPath, true); logger.debug('SftpStorageProvider', `Created directory: ${fullPath}`); } catch (error) { logger.error('SftpStorageProvider', `Failed to create directory: ${error}`); throw error; } } async readFile(path) { await this.ensureConnected(); try { const fullPath = this.getFullPath(path); const buffer = await this.client.get(fullPath); return buffer.toString('utf-8'); } catch (error) { logger.error('SftpStorageProvider', `Failed to read file: ${error}`); throw error; } } async writeFile(path, content) { await this.ensureConnected(); try { const fullPath = this.getFullPath(path); await this.client.put(Buffer.from(content, 'utf-8'), fullPath); logger.debug('SftpStorageProvider', `Wrote file: ${fullPath}`); } catch (error) { logger.error('SftpStorageProvider', `Failed to write file: ${error}`); throw error; } } async listFiles(path) { try { const list = await this.client.list(path); return list.map((item) => item.name); } catch (error) { console.error(`Failed to list files in: ${path}`, error); throw error; } } async getFileStats(path) { try { const stats = await this.client.stat(path); return { mtimeMs: stats.modifyTime * 1000 }; } catch (error) { console.error(`Failed to get file stats for: ${path}`, error); throw error; } } async getStatus(path) { await this.ensureConnected(); try { const fullPath = this.getFullPath(path); const files = await this.listFiles(fullPath); const coreFiles = [ 'product-context.md', 'active-context.md', 'progress.md', 'decision-log.md', 'system-patterns.md' ]; const missingCoreFiles = coreFiles.filter(file => !files.includes(file)); const coreFilesPresent = coreFiles.filter(file => files.includes(file)); let lastUpdated; if (files.length > 0) { const stats = await Promise.all(files.map(file => this.getFileStats(path + '/' + file))); const latestMtime = Math.max(...stats.map(stat => stat.mtimeMs)); lastUpdated = new Date(latestMtime); } return { path: fullPath, files, coreFilesPresent, missingCoreFiles, isComplete: missingCoreFiles.length === 0, language: 'en', lastUpdated }; } catch (error) { logger.error('SftpStorageProvider', `Failed to get status: ${error}`); throw error; } } async createBackup(sourcePath, backupPath) { await this.ensureConnected(); try { const fullSourcePath = this.getFullPath(sourcePath); const fullBackupPath = this.getFullPath(backupPath); // Create backup directory await this.createDirectory(fullBackupPath); // Copy all files from source to backup const files = await this.listFiles(fullSourcePath); for (const file of files) { const sourceFile = `${fullSourcePath}/${file}`; const backupFile = `${fullBackupPath}/${file}`; await this.client.fastGet(sourceFile, backupFile); } logger.debug('SftpStorageProvider', `Created backup at: ${fullBackupPath}`); } catch (error) { logger.error('SftpStorageProvider', `Failed to create backup: ${error}`); throw error; } } getFullPath(path) { return `${this.config.basePath}/${path}`.replace(/\/+/g, '/'); } async disconnect() { if (this.connected) { try { await this.client.end(); this.connected = false; logger.debug('SftpStorageProvider', 'Disconnected from SFTP server'); } catch (error) { logger.error('SftpStorageProvider', `Failed to disconnect from SFTP server: ${error}`); throw error; } } } async ensureDirectoryExists(path) { try { const exists = await this.client.exists(path); if (!exists) { await this.client.mkdir(path, true); } } catch (error) { logger.error('SftpStorageProvider', `Failed to ensure directory exists: ${error}`); throw error; } } async deleteFile(path) { try { await this.client.delete(path); } catch (error) { logger.error('SftpStorageProvider', `Failed to delete file: ${error}`); throw error; } } async initializeMemoryBank(config) { const basePath = this.config.basePath; await this.ensureDirectoryExists(basePath); const files = [ 'product-context.md', 'active-context.md', 'progress.md', 'decision-log.md', 'system-patterns.md' ]; for (const file of files) { const filePath = join(basePath, file); const exists = await this.client.exists(filePath); if (!exists) { await this.writeFile(filePath, ''); } } } async findMemoryBank(path) { try { const exists = await this.client.exists(path); return exists ? path : null; } catch (error) { logger.error('SftpStorageProvider', `Failed to find memory bank at: ${error}`); return null; } } } //# sourceMappingURL=SftpStorageProvider.js.map