mcp-prompt-optimizer-local
Version:
Advanced cross-platform prompt optimization with MCP integration and 120+ optimization rules
314 lines (268 loc) • 10.7 kB
JavaScript
/**
* 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;