UNPKG

mcp-prompt-optimizer-local

Version:

Advanced cross-platform prompt optimization with MCP integration and 120+ optimization rules

314 lines (268 loc) 10.7 kB
/** * Simplified License Manager - Production Ready with Quota Enforcement */ const https = require('https'); const fs = require('fs').promises; const path = require('path'); const os = require('os'); class SimplifiedLicenseManager { constructor() { this.cacheFile = path.join(os.homedir(), '.mcp-license-cache.json'); this.usageFile = path.join(os.homedir(), '.mcp-optimizer-usage.json'); this.cacheExpiry = 24 * 60 * 60 * 1000; // 24 hours this.debug = process.env.MCP_DEBUG === 'true'; // Only debug if explicitly enabled } log(message) { if (this.debug) { console.log(`[DEBUG] ${message}`); } } async validateLicense() { const apiKey = process.env.OPTIMIZER_API_KEY; this.log(`Starting license validation...`); this.log(`API Key found: ${apiKey ? 'YES' : 'NO'}`); if (!apiKey) { // NEW: Return free tier instead of error this.log(`No API key - returning FREE tier`); return { valid: true, type: 'free', tier: 'free', features: [ 'local_processing', 'basic_optimization' ], quota: { daily_limit: 5, remaining: 5 }, free_tier: true }; } this.log(`API Key: ${apiKey.substring(0, 25)}...`); // Validate format using original logic if (!this.isValidKeyFormat(apiKey)) { this.log(`Key format validation failed`); return { valid: false, error: 'Invalid API key format. Must start with "sk-local-" and contain "-basic-" or "-pro-"' }; } this.log(`Key format validation passed`); // Try cache first const cached = await this.getCachedValidation(apiKey); if (cached && cached.valid) { this.log(`Using cached validation - VALID`); return cached; } else if (cached) { this.log(`Cached validation found but INVALID - ignoring cache`); } this.log(`No valid cache found, using local validation...`); // For production, use local validation as primary method // This ensures reliability while backend integration can be added later if (this.isValidKeyFormat(apiKey)) { this.log(`Using local validation for production`); const result = { valid: true, type: apiKey.includes('-pro-') ? 'local_pro' : 'local_basic', tier: apiKey.includes('-pro-') ? 'pro' : 'basic', features: [ 'local_processing', 'template_management', 'debugging_enhancement', 'content_analysis', 'technical_preservation' ], quota: apiKey.includes('-pro-') ? { unlimited: true } : { daily_limit: 5, remaining: 5 }, local_validation: true }; // Try to cache it await this.cacheValidation(apiKey, result); return result; } // If we get here, something is wrong return { valid: false, error: 'License validation failed' }; } // NEW: Load daily usage from persistent storage async loadDailyUsage() { try { const data = await fs.readFile(this.usageFile, 'utf8'); const usage = JSON.parse(data); // Reset if new day const today = new Date().toDateString(); if (usage.date !== today) { this.log(`New day detected, resetting usage count`); return { date: today, count: 0 }; } this.log(`Daily usage loaded: ${usage.count} optimizations used today`); return usage; } catch (error) { this.log(`No usage file found, creating new: ${error.message}`); const newUsage = { date: new Date().toDateString(), count: 0 }; await this.saveDailyUsage(newUsage); return newUsage; } } // NEW: Save daily usage to persistent storage async saveDailyUsage(usage) { try { await fs.writeFile(this.usageFile, JSON.stringify(usage, null, 2)); this.log(`Usage saved: ${usage.count} optimizations on ${usage.date}`); } catch (error) { this.log(`Failed to save usage: ${error.message}`); } } // NEW: Increment daily usage count async incrementUsageCount() { const usage = await this.loadDailyUsage(); usage.count++; await this.saveDailyUsage(usage); this.log(`Usage incremented to: ${usage.count}`); return usage; } // NEW: Check if user has exceeded daily quota async checkDailyQuota(licenseResult) { // Pro/unlimited users have no quota if (!licenseResult.quota || licenseResult.quota.unlimited) { this.log(`Unlimited quota - no checking needed`); return { allowed: true, remaining: 'unlimited' }; } const usage = await this.loadDailyUsage(); const remaining = Math.max(0, licenseResult.quota.daily_limit - usage.count); const allowed = usage.count < licenseResult.quota.daily_limit; this.log(`Quota check: ${usage.count}/${licenseResult.quota.daily_limit} used, remaining: ${remaining}`); return { allowed, remaining, used: usage.count, limit: licenseResult.quota.daily_limit, resetsAt: new Date(new Date().getTime() + (24 * 60 * 60 * 1000)).toISOString() }; } // NEW: Combined license validation and quota checking async validateLicenseAndQuota() { const license = await this.validateLicense(); if (!license.valid) { return license; } const quota = await this.checkDailyQuota(license); return { ...license, quota: { ...license.quota, ...quota } }; } // NEW: Reset quota for testing (TEST ENVIRONMENTS ONLY) async resetQuotaForTesting() { if (process.env.NODE_ENV === 'production') { throw new Error('Quota reset not allowed in production'); } try { const resetUsage = { date: new Date().toDateString(), count: 0, resetForTesting: true, resetTime: new Date().toISOString() }; await this.saveDailyUsage(resetUsage); this.log(`Quota reset for testing: ${resetUsage.resetTime}`); return resetUsage; } catch (error) { this.log(`Failed to reset quota: ${error.message}`); throw error; } } // Use the exact validation logic from the original working version isValidKeyFormat(licenseKey) { if (!licenseKey || typeof licenseKey !== 'string') { this.log(`Key validation failed: not a string`); return false; } // Must start with sk-local- if (!licenseKey.startsWith('sk-local-')) { this.log(`Key validation failed: doesn't start with sk-local-`); return false; } // Must have basic or pro tier if (!licenseKey.includes('-basic-') && !licenseKey.includes('-pro-')) { this.log(`Key validation failed: doesn't contain -basic- or -pro-`); return false; } // Minimum length check if (licenseKey.length < 25) { this.log(`Key validation failed: too short (${licenseKey.length} chars)`); return false; } this.log(`Key format validation: PASSED`); return true; } async getCachedValidation(apiKey, allowExpired = false) { try { const cacheData = await fs.readFile(this.cacheFile, 'utf8'); const cache = JSON.parse(cacheData); this.log(`Cache file contents: ${JSON.stringify(cache, null, 2)}`); if (cache.apiKey !== apiKey) { this.log(`Cache key mismatch`); return null; } const age = Date.now() - new Date(cache.timestamp).getTime(); if (!allowExpired && age > this.cacheExpiry) { this.log(`Cache expired (${Math.round(age / 1000 / 60)} minutes old)`); return null; } this.log(`Cache found - Valid: ${cache.validation?.valid}`); return cache.validation; } catch (error) { this.log(`No cache available: ${error.message}`); return null; } } async cacheValidation(apiKey, validation) { try { const cacheData = { apiKey, validation, timestamp: new Date().toISOString() }; await fs.writeFile(this.cacheFile, JSON.stringify(cacheData, null, 2)); this.log(`Validation cached successfully`); } catch (error) { this.log(`Cache write failed: ${error.message}`); } } async clearCache() { try { await fs.unlink(this.cacheFile); this.log(`Cache file deleted: ${this.cacheFile}`); } catch (error) { this.log(`Cache clear failed (file may not exist): ${error.message}`); } } // NEW: Get current quota status for tools async getQuotaStatus() { const license = await this.validateLicense(); if (!license.valid) { return { error: 'Invalid license' }; } if (!license.quota || license.quota.unlimited) { return { tier: license.tier || license.type, unlimited: true, message: 'Unlimited optimizations available' }; } const usage = await this.loadDailyUsage(); const remaining = Math.max(0, license.quota.daily_limit - usage.count); return { tier: license.tier || license.type, unlimited: false, used: usage.count, remaining, limit: license.quota.daily_limit, resetsAt: new Date(new Date().setHours(24, 0, 0, 0)).toISOString() }; } } module.exports = SimplifiedLicenseManager;