UNPKG

lsh-framework

Version:

A powerful, extensible shell with advanced job management, database persistence, and modern CLI features

247 lines (246 loc) 8.59 kB
/** * Enhanced History System with Supabase Integration * Extends the base history system with cloud persistence */ import HistorySystem from './history-system.js'; import DatabasePersistence from './database-persistence.js'; import * as os from 'os'; import * as path from 'path'; export class EnhancedHistorySystem extends HistorySystem { databasePersistence; enhancedConfig; syncTimer; pendingSync = false; constructor(config = {}) { const defaultConfig = { maxSize: 10000, filePath: path.join(os.homedir(), '.lsh_history'), shareHistory: true, ignoreDups: true, ignoreSpace: true, expireDuplicatesFirst: true, enableCloudSync: true, userId: undefined, syncInterval: 30000, // 30 seconds ...config, }; super(defaultConfig); this.enhancedConfig = defaultConfig; this.databasePersistence = new DatabasePersistence(defaultConfig.userId); if (this.enhancedConfig.enableCloudSync) { this.initializeCloudSync(); } } /** * Initialize cloud synchronization */ async initializeCloudSync() { try { const isConnected = await this.databasePersistence.testConnection(); if (isConnected) { console.log('✅ Cloud history sync enabled'); await this.loadCloudHistory(); this.startSyncTimer(); } else { console.log('⚠️ Cloud history sync disabled - database not available'); } } catch (error) { console.error('Failed to initialize cloud sync:', error); } } /** * Load history from cloud database */ async loadCloudHistory() { try { const cloudEntries = await this.databasePersistence.getHistoryEntries(1000); // Convert cloud entries to local format const localEntries = cloudEntries.map((entry, index) => ({ lineNumber: index + 1, command: entry.command, timestamp: new Date(entry.timestamp).getTime(), exitCode: entry.exit_code, })); // Merge with local history (avoid duplicates) this.mergeHistoryEntries(localEntries); } catch (error) { console.error('Failed to load cloud history:', error); } } /** * Merge cloud history entries with local history */ mergeHistoryEntries(cloudEntries) { const localEntries = this.getAllEntries(); const mergedEntries = []; // Create a map of local entries by command and timestamp const localMap = new Map(); localEntries.forEach(entry => { const key = `${entry.command}_${entry.timestamp}`; localMap.set(key, entry); }); // Add cloud entries that don't exist locally cloudEntries.forEach(entry => { const key = `${entry.command}_${entry.timestamp}`; if (!localMap.has(key)) { mergedEntries.push(entry); } }); // Add all local entries mergedEntries.push(...localEntries); // Sort by timestamp and update line numbers mergedEntries.sort((a, b) => a.timestamp - b.timestamp); mergedEntries.forEach((entry, index) => { entry.lineNumber = index + 1; }); // Update internal entries this.entries = mergedEntries; } /** * Start periodic synchronization timer */ startSyncTimer() { this.syncTimer = setInterval(() => { this.syncToCloud(); }, this.enhancedConfig.syncInterval); } /** * Stop synchronization timer */ stopSyncTimer() { if (this.syncTimer) { clearInterval(this.syncTimer); this.syncTimer = undefined; } } /** * Synchronize local history to cloud */ async syncToCloud() { if (this.pendingSync || !this.enhancedConfig.enableCloudSync) { return; } this.pendingSync = true; try { const localEntries = this.getAllEntries(); const recentEntries = localEntries.slice(-50); // Sync last 50 entries for (const entry of recentEntries) { await this.databasePersistence.saveHistoryEntry({ session_id: this.databasePersistence.getSessionId(), command: entry.command, working_directory: process.cwd(), exit_code: entry.exitCode, timestamp: new Date(entry.timestamp).toISOString(), hostname: os.hostname(), }); } } catch (error) { console.error('Failed to sync history to cloud:', error); } finally { this.pendingSync = false; } } /** * Override addCommand to include cloud sync */ addCommand(command, exitCode) { super.addCommand(command, exitCode); // Sync to cloud if enabled if (this.enhancedConfig.enableCloudSync) { this.syncToCloud().catch(error => { console.error('Failed to sync command to cloud:', error); }); } } /** * Search history across local and cloud data */ async searchHistoryCloud(query, limit = 20) { const localResults = this.searchHistory(query).slice(0, limit); if (!this.enhancedConfig.enableCloudSync) { return localResults; } try { // Search cloud history const cloudEntries = await this.databasePersistence.getHistoryEntries(1000); const cloudResults = cloudEntries .filter(entry => entry.command.toLowerCase().includes(query.toLowerCase())) .slice(0, limit) .map((entry, index) => ({ lineNumber: index + 1, command: entry.command, timestamp: new Date(entry.timestamp).getTime(), exitCode: entry.exit_code, })); // Merge and deduplicate results const allResults = [...localResults, ...cloudResults]; const uniqueResults = allResults.filter((entry, index, arr) => arr.findIndex(e => e.command === entry.command && e.timestamp === entry.timestamp) === index); return uniqueResults.slice(0, limit); } catch (error) { console.error('Failed to search cloud history:', error); return localResults; } } /** * Get history statistics */ async getHistoryStats() { const localEntries = this.getAllEntries(); let cloudEntries = 0; if (this.enhancedConfig.enableCloudSync) { try { const cloudData = await this.databasePersistence.getHistoryEntries(10000); cloudEntries = cloudData.length; } catch (error) { console.error('Failed to get cloud history stats:', error); } } // Calculate statistics const commandCounts = new Map(); let totalLength = 0; localEntries.forEach(entry => { commandCounts.set(entry.command, (commandCounts.get(entry.command) || 0) + 1); totalLength += entry.command.length; }); const mostUsedCommand = Array.from(commandCounts.entries()) .sort(([, a], [, b]) => b - a)[0]?.[0] || ''; return { localEntries: localEntries.length, cloudEntries, totalCommands: localEntries.length + cloudEntries, mostUsedCommand, averageCommandLength: localEntries.length > 0 ? totalLength / localEntries.length : 0, }; } /** * Enable or disable cloud sync */ setCloudSyncEnabled(enabled) { this.enhancedConfig.enableCloudSync = enabled; if (enabled) { this.initializeCloudSync(); } else { this.stopSyncTimer(); } } /** * Cleanup resources */ destroy() { this.stopSyncTimer(); if (this.enhancedConfig.enableCloudSync) { this.syncToCloud().catch(error => { console.error('Failed to final sync on destroy:', error); }); } } } export default EnhancedHistorySystem;