UNPKG

@every-env/sparkle-mcp-server

Version:

MCP server for secure Sparkle folder file access with Claude AI, including clipboard history support

320 lines 11.4 kB
import * as fs from "fs/promises"; import * as path from "path"; import * as os from "os"; export class ClipboardHistoryManager { pasteboardPath; constructor(sparklePath) { this.pasteboardPath = path.join(this.expandPath(sparklePath), "Pasteboard"); } expandPath(folderPath) { if (folderPath.startsWith("~/")) { return path.join(os.homedir(), folderPath.slice(2)); } return folderPath; } /** * Search clipboard history with various filters */ async searchClipboardHistory(options) { const { query, startDate, endDate, type, limit = 50 } = options; const results = []; try { // Get all date files const dateFiles = await this.getDateFiles(startDate, endDate); for (const dateFile of dateFiles) { const dayResults = await this.searchDayClipboard(dateFile, query, type, limit); if (dayResults.entries.length > 0) { results.push(dayResults); } // Stop if we've reached the limit const totalEntries = results.reduce((sum, r) => sum + r.entries.length, 0); if (totalEntries >= limit) { break; } } return results; } catch (error) { console.error("Error searching clipboard history:", error); return []; } } /** * Get clipboard entries for a specific date */ async getClipboardByDate(date) { const dateStr = this.formatDate(date); try { const entries = await this.readDateClipboard(dateStr); return { date: dateStr, entries, totalCount: entries.length }; } catch (error) { return { date: dateStr, entries: [], totalCount: 0 }; } } /** * Get recent clipboard entries (last N days) */ async getRecentClipboard(days = 7, limit = 50) { const endDate = new Date(); const startDate = new Date(); startDate.setDate(startDate.getDate() - days); return this.searchClipboardHistory({ startDate, endDate, limit }); } /** * Search for clipboard entries containing specific text */ async findClipboardText(searchText, limit = 30) { const results = await this.searchClipboardHistory({ query: searchText, limit }); // Flatten results const entries = []; for (const result of results) { entries.push(...result.entries); if (entries.length >= limit) { return entries.slice(0, limit); } } return entries; } /** * Get available date files */ async getDateFiles(startDate, endDate) { try { const entries = await fs.readdir(this.pasteboardPath, { withFileTypes: true }); let dateFiles = entries .filter(entry => entry.isFile() && /^\d{4}-\d{2}-\d{2}_clipboard\.(txt|json)$/.test(entry.name)) .map(entry => entry.name.replace(/_clipboard\.(txt|json)$/, '')) .sort() .reverse(); // Most recent first // Filter by date range if provided if (startDate || endDate) { dateFiles = dateFiles.filter(dateStr => { const date = new Date(dateStr); if (startDate && date < startDate) return false; if (endDate && date > endDate) return false; return true; }); } return dateFiles; } catch (error) { console.error("Error reading pasteboard directory:", error); return []; } } /** * Search clipboard entries for a specific day */ async searchDayClipboard(dateStr, query, type, limit) { const entries = await this.readDateClipboard(dateStr); let filtered = entries; // Filter by query if (query) { const queryLower = query.toLowerCase(); filtered = filtered.filter(entry => entry.content.toLowerCase().includes(queryLower) || entry.metadata?.app?.toLowerCase().includes(queryLower)); } // Filter by type if (type) { filtered = filtered.filter(entry => entry.type === type); } // Apply limit if (limit && filtered.length > limit) { filtered = filtered.slice(0, limit); } return { date: dateStr, entries: filtered, totalCount: entries.length }; } /** * Read clipboard entries from a date file */ async readDateClipboard(dateStr) { const entries = []; try { // Try both .txt and .json formats const possibleFiles = [ `${dateStr}_clipboard.json`, // JSON format: 2025-01-08_clipboard.json `${dateStr}_clipboard.txt` // Text format: 2025-01-08_clipboard.txt ]; for (const filename of possibleFiles) { const filePath = path.join(this.pasteboardPath, filename); try { const content = await fs.readFile(filePath, 'utf-8'); if (filename.endsWith('.json')) { // Parse JSON format const data = JSON.parse(content); if (Array.isArray(data)) { entries.push(...data.map((entry) => this.normalizeEntry(entry))); } else if (data.entries && Array.isArray(data.entries)) { entries.push(...data.entries.map((entry) => this.normalizeEntry(entry))); } } else { // Parse text format - each line is a clipboard entry // Format: "TIMESTAMP | TYPE | CONTENT" const lines = content.split('\n').filter(line => line.trim()); for (const line of lines) { const entry = this.parseTextEntry(line, dateStr); if (entry.content.trim()) { entries.push(entry); } } } break; // Found a file, stop looking } catch (error) { // File doesn't exist or can't be read, try next format continue; } } // Sort by timestamp (most recent first) return entries.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()); } catch (error) { console.error(`Error reading clipboard for date ${dateStr}:`, error); return []; } } /** * Normalize entry data from various formats */ normalizeEntry(data) { return { timestamp: data.timestamp ? new Date(data.timestamp) : new Date(), content: data.content || data.text || data.data || '', type: data.type || this.inferType(data.content || ''), metadata: { app: data.app || data.source || undefined, size: data.size || data.content?.length || undefined, format: data.format || undefined } }; } /** * Parse text entry (for simple text format) */ parseTextEntry(line, dateStr) { // Try to parse structured format like: "14:30:00 | text | Content here" // or "2024-01-15 14:30:00 | text | Content here" const parts = line.split('|').map(p => p.trim()); if (parts.length >= 3) { let timestampStr = parts[0]; // If timestamp doesn't include date, prepend the date if (!timestampStr.includes('-') && timestampStr.includes(':')) { timestampStr = `${dateStr} ${timestampStr}`; } return { timestamp: new Date(timestampStr), type: parts[1], content: parts.slice(2).join('|').trim() }; } // If only 2 parts, assume: "timestamp | content" if (parts.length === 2) { let timestampStr = parts[0]; if (!timestampStr.includes('-') && timestampStr.includes(':')) { timestampStr = `${dateStr} ${timestampStr}`; } return { timestamp: new Date(timestampStr), content: parts[1].trim(), type: this.inferType(parts[1]) }; } // Fall back to simple content with date from filename return { timestamp: new Date(dateStr), content: line.trim(), type: this.inferType(line) }; } /** * Extract timestamp from filename */ extractTimestampFromFilename(filename) { // Try to extract timestamp from filenames like "2024-01-15-143000.txt" const match = filename.match(/(\d{4}-\d{2}-\d{2})[-_]?(\d{6})?/); if (match) { const dateStr = match[1]; const timeStr = match[2] || '000000'; return new Date(`${dateStr}T${timeStr.substr(0, 2)}:${timeStr.substr(2, 2)}:${timeStr.substr(4, 2)}`); } return null; } /** * Infer content type */ inferType(content) { if (content.startsWith('http://') || content.startsWith('https://')) { return 'url'; } if (content.startsWith('/') && content.includes('.')) { return 'file-path'; } if (content.match(/^data:image\//)) { return 'image'; } return 'text'; } /** * Format date as YYYY-MM-DD */ formatDate(date) { return date.toISOString().split('T')[0]; } /** * Get date from date string */ getDateFromString(dateStr) { return new Date(dateStr); } /** * Get clipboard statistics */ async getClipboardStats() { const dateFiles = await this.getDateFiles(); let totalEntries = 0; const typeBreakdown = {}; const recentActivity = []; for (const dateFile of dateFiles.slice(0, 30)) { // Last 30 days const result = await this.searchDayClipboard(dateFile); totalEntries += result.totalCount; recentActivity.push({ date: dateFile, count: result.totalCount }); for (const entry of result.entries) { typeBreakdown[entry.type] = (typeBreakdown[entry.type] || 0) + 1; } } return { totalDays: dateFiles.length, totalEntries, typeBreakdown, recentActivity: recentActivity.slice(0, 7) // Last 7 days }; } } //# sourceMappingURL=clipboard-history.js.map