@promptordie/siphon-knowledge
Version:
AI-powered documentation generation system for AI Coding Agents.
377 lines (321 loc) โข 13.4 kB
text/typescript
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();
}