UNPKG

@promptordie/siphon-knowledge

Version:

AI-powered documentation generation system for AI Coding Agents.

377 lines (321 loc) โ€ข 13.4 kB
import { readFile, writeFile, mkdir } from "node:fs/promises"; import { existsSync } from "node:fs"; import path from "node:path"; import { logger } from "../logger.ts"; export interface UserPreferences { dataProcessingLevel: 'full' | 'categorized' | 'polished'; includeMetadata: boolean; includeRawHtml: boolean; includeScreenshots: boolean; cleanupOldData: boolean; organizeByDate: boolean; compressOutput: boolean; outputDirectory: string; maxConcurrentScrapes: number; timeoutSeconds: number; retryAttempts: number; qualityThreshold: number; preferredCategories: string[]; excludedPatterns: string[]; autoCleanupDays: number; saveProcessingHistory: boolean; exportFormat: 'markdown' | 'json' | 'html' | 'all'; lastUpdated: string; } export const DEFAULT_PREFERENCES: UserPreferences = { dataProcessingLevel: 'categorized', includeMetadata: true, includeRawHtml: false, includeScreenshots: false, cleanupOldData: true, organizeByDate: true, compressOutput: false, outputDirectory: 'organized-data', maxConcurrentScrapes: 5, timeoutSeconds: 30, retryAttempts: 2, qualityThreshold: 50, preferredCategories: [], excludedPatterns: ['admin', 'private', 'temp'], autoCleanupDays: 7, saveProcessingHistory: true, exportFormat: 'markdown', lastUpdated: new Date().toISOString() }; export class UserPreferenceManager { private preferencesPath: string; private preferences: UserPreferences; constructor(preferencesPath?: string) { this.preferencesPath = preferencesPath || '.cursor/user-preferences.json'; this.preferences = { ...DEFAULT_PREFERENCES }; } async loadPreferences(): Promise<UserPreferences> { try { if (existsSync(this.preferencesPath)) { const data = await readFile(this.preferencesPath, 'utf8'); const loaded = JSON.parse(data); // Merge with defaults to ensure all properties exist this.preferences = { ...DEFAULT_PREFERENCES, ...loaded }; this.preferences.lastUpdated = new Date().toISOString(); logger.success("โœ… User preferences loaded successfully"); return this.preferences; } else { logger.info("๐Ÿ“ No existing preferences found, using defaults"); await this.savePreferences(); return this.preferences; } } catch (error) { logger.warn(`โš ๏ธ Error loading preferences: ${(error as Error).message}`); logger.info("๐Ÿ”„ Using default preferences"); return this.preferences; } } async savePreferences(): Promise<void> { try { // Ensure directory exists const dir = path.dirname(this.preferencesPath); if (!existsSync(dir)) { await mkdir(dir, { recursive: true }); } this.preferences.lastUpdated = new Date().toISOString(); await writeFile(this.preferencesPath, JSON.stringify(this.preferences, null, 2), 'utf8'); logger.success("๐Ÿ’พ User preferences saved successfully"); } catch (error) { logger.error(`โŒ Error saving preferences: ${(error as Error).message}`); } } async updatePreferences(updates: Partial<UserPreferences>): Promise<void> { this.preferences = { ...this.preferences, ...updates }; await this.savePreferences(); } getPreferences(): UserPreferences { return { ...this.preferences }; } async resetToDefaults(): Promise<void> { this.preferences = { ...DEFAULT_PREFERENCES }; await this.savePreferences(); logger.info("๐Ÿ”„ Preferences reset to defaults"); } async showCurrentPreferences(): Promise<void> { logger.info("\n๐ŸŽฏ Current User Preferences:"); logger.info("=".repeat(50)); const prefs = this.getPreferences(); logger.info(`๐Ÿ“Š Data Processing Level: ${prefs.dataProcessingLevel}`); logger.info(`๐Ÿ“ Output Directory: ${prefs.outputDirectory}`); logger.info(`๐Ÿ”ง Include Metadata: ${prefs.includeMetadata ? 'Yes' : 'No'}`); logger.info(`๐ŸŒ Include Raw HTML: ${prefs.includeRawHtml ? 'Yes' : 'No'}`); logger.info(`๐Ÿ“ธ Include Screenshots: ${prefs.includeScreenshots ? 'Yes' : 'No'}`); logger.info(`๐Ÿงน Cleanup Old Data: ${prefs.cleanupOldData ? 'Yes' : 'No'}`); logger.info(`๐Ÿ“… Organize by Date: ${prefs.organizeByDate ? 'Yes' : 'No'}`); logger.info(`๐Ÿ—œ๏ธ Compress Output: ${prefs.compressOutput ? 'Yes' : 'No'}`); logger.info(`โšก Max Concurrent Scrapes: ${prefs.maxConcurrentScrapes}`); logger.info(`โฑ๏ธ Timeout: ${prefs.timeoutSeconds}s`); logger.info(`๐Ÿ”„ Retry Attempts: ${prefs.retryAttempts}`); logger.info(`๐ŸŽฏ Quality Threshold: ${prefs.qualityThreshold}/100`); logger.info(`๐Ÿ—‘๏ธ Auto Cleanup: ${prefs.autoCleanupDays} days`); logger.info(`๐Ÿ’พ Save History: ${prefs.saveProcessingHistory ? 'Yes' : 'No'}`); logger.info(`๐Ÿ“ค Export Format: ${prefs.exportFormat}`); logger.info(`๐Ÿ“… Last Updated: ${prefs.lastUpdated}`); if (prefs.preferredCategories.length > 0) { logger.info(`โญ Preferred Categories: ${prefs.preferredCategories.join(', ')}`); } if (prefs.excludedPatterns.length > 0) { logger.info(`๐Ÿšซ Excluded Patterns: ${prefs.excludedPatterns.join(', ')}`); } logger.info("=".repeat(50)); } async interactiveSetup(): Promise<void> { logger.info("\n๐ŸŽฏ Interactive Preference Setup"); logger.info("Let's configure your data processing preferences...\n"); // Data processing level logger.info("Choose your data processing level:"); logger.info("1. Full Data - Complete content with all metadata"); logger.info("2. Categorized Data - Organized content with summaries"); logger.info("3. Polished Data - Clean, refined content for users"); // For now, we'll use a simple selection // In a real implementation, this would be interactive const level = 'categorized'; // Default selection logger.info(`Selected: ${level}`); // Update preferences based on selection const updates: Partial<UserPreferences> = { dataProcessingLevel: level as 'full' | 'categorized' | 'polished', includeMetadata: level === 'full', includeRawHtml: level === 'full', includeScreenshots: level === 'full' }; await this.updatePreferences(updates); logger.success("โœ… Interactive setup completed!"); await this.showCurrentPreferences(); } getProcessingOptions() { const prefs = this.getPreferences(); return { level: prefs.dataProcessingLevel, includeMetadata: prefs.includeMetadata, includeRawHtml: prefs.includeRawHtml, includeScreenshots: prefs.includeScreenshots, cleanupOldData: prefs.cleanupOldData, organizeByDate: prefs.organizeByDate, compressOutput: prefs.compressOutput }; } async exportPreferences(format: 'json' | 'markdown' | 'html' = 'json'): Promise<string> { const prefs = this.getPreferences(); switch (format) { case 'json': return JSON.stringify(prefs, null, 2); case 'markdown': return `# User Preferences **Last Updated:** ${prefs.lastUpdated} ## Data Processing - **Level:** ${prefs.dataProcessingLevel} - **Include Metadata:** ${prefs.includeMetadata} - **Include Raw HTML:** ${prefs.includeRawHtml} - **Include Screenshots:** ${prefs.includeScreenshots} ## Organization - **Output Directory:** ${prefs.outputDirectory} - **Organize by Date:** ${prefs.organizeByDate} - **Compress Output:** ${prefs.compressOutput} ## Performance - **Max Concurrent Scrapes:** ${prefs.maxConcurrentScrapes} - **Timeout:** ${prefs.timeoutSeconds}s - **Retry Attempts:** ${prefs.retryAttempts} ## Quality & Cleanup - **Quality Threshold:** ${prefs.qualityThreshold}/100 - **Auto Cleanup:** ${prefs.autoCleanupDays} days - **Cleanup Old Data:** ${prefs.cleanupOldData} ## Export & History - **Export Format:** ${prefs.exportFormat} - **Save Processing History:** ${prefs.saveProcessingHistory} ## Categories - **Preferred:** ${prefs.preferredCategories.join(', ') || 'None'} - **Excluded:** ${prefs.excludedPatterns.join(', ') || 'None'} `; case 'html': return `<!DOCTYPE html> <html> <head> <title>User Preferences</title> <style> body { font-family: Arial, sans-serif; margin: 40px; } .section { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; } .key { font-weight: bold; color: #333; } .value { color: #666; } </style> </head> <body> <h1>User Preferences</h1> <p><strong>Last Updated:</strong> ${prefs.lastUpdated}</p> <div class="section"> <h2>Data Processing</h2> <p><span class="key">Level:</span> <span class="value">${prefs.dataProcessingLevel}</span></p> <p><span class="key">Include Metadata:</span> <span class="value">${prefs.includeMetadata}</span></p> <p><span class="key">Include Raw HTML:</span> <span class="value">${prefs.includeRawHtml}</span></p> <p><span class="key">Include Screenshots:</span> <span class="value">${prefs.includeScreenshots}</span></p> </div> <div class="section"> <h2>Organization</h2> <p><span class="key">Output Directory:</span> <span class="value">${prefs.outputDirectory}</span></p> <p><span class="key">Organize by Date:</span> <span class="value">${prefs.organizeByDate}</span></p> <p><span class="key">Compress Output:</span> <span class="value">${prefs.compressOutput}</span></p> </div> <div class="section"> <h2>Performance</h2> <p><span class="key">Max Concurrent Scrapes:</span> <span class="value">${prefs.maxConcurrentScrapes}</span></p> <p><span class="key">Timeout:</span> <span class="value">${prefs.timeoutSeconds}s</span></p> <p><span class="key">Retry Attempts:</span> <span class="value">${prefs.retryAttempts}</span></p> </div> <div class="section"> <h2>Quality & Cleanup</h2> <p><span class="key">Quality Threshold:</span> <span class="value">${prefs.qualityThreshold}/100</span></p> <p><span class="key">Auto Cleanup:</span> <span class="value">${prefs.autoCleanupDays} days</span></p> <p><span class="key">Cleanup Old Data:</span> <span class="value">${prefs.cleanupOldData}</span></p> </div> </body> </html>`; default: return JSON.stringify(prefs, null, 2); } } } // CLI interface for managing preferences async function main(): Promise<void> { try { const args = process.argv.slice(2); const manager = new UserPreferenceManager(); if (args.length === 0) { // Show current preferences await manager.loadPreferences(); await manager.showCurrentPreferences(); return; } const command = args[0]; switch (command) { case 'show': await manager.loadPreferences(); await manager.showCurrentPreferences(); break; case 'setup': await manager.loadPreferences(); await manager.interactiveSetup(); break; case 'reset': await manager.resetToDefaults(); break; case 'export': const format = args[1] as 'json' | 'markdown' | 'html' || 'json'; await manager.loadPreferences(); const exported = await manager.exportPreferences(format); console.log(exported); break; case 'update': if (args.length < 3) { logger.error("Usage: update <key> <value>"); return; } const key = args[1]; const value = args[2]; // Simple key-value updates const updates: any = {}; if (key === 'level' && ['full', 'categorized', 'polished'].includes(value)) { updates.dataProcessingLevel = value; } else if (key === 'output') { updates.outputDirectory = value; } else if (key === 'timeout') { updates.timeoutSeconds = parseInt(value) || 30; } else if (key === 'retries') { updates.retryAttempts = parseInt(value) || 2; } else if (key === 'quality') { updates.qualityThreshold = parseInt(value) || 50; } else if (key === 'cleanup') { updates.autoCleanupDays = parseInt(value) || 7; } else { logger.error(`Unknown key: ${key}`); return; } await manager.loadPreferences(); await manager.updatePreferences(updates); logger.success(`โœ… Updated ${key} to ${value}`); break; default: logger.info("Available commands:"); logger.info(" show - Show current preferences"); logger.info(" setup - Interactive setup"); logger.info(" reset - Reset to defaults"); logger.info(" export - Export preferences (json|markdown|html)"); logger.info(" update - Update specific preference (key value)"); logger.info(""); logger.info("Examples:"); logger.info(" bun run user-preferences.ts show"); logger.info(" bun run user-preferences.ts setup"); logger.info(" bun run user-preferences.ts update level full"); logger.info(" bun run user-preferences.ts export markdown"); } } catch (error) { logger.error(`โŒ Error: ${(error as Error).message}`); process.exit(1); } } if (import.meta.main) { main(); }