Claude-skill-registry evernote-cost-tuning
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/evernote-cost-tuning" ~/.claude/skills/majiayu000-claude-skill-registry-evernote-cost-tuning && rm -rf "$T"
manifest:
skills/data/evernote-cost-tuning/SKILL.mdsource content
Evernote Cost Tuning
Overview
Optimize resource usage and manage costs in Evernote integrations, focusing on upload quotas, storage efficiency, and account limits.
Prerequisites
- Understanding of Evernote account tiers
- Access to user quota information
- Monitoring infrastructure
Account Limits by Tier
| Feature | Basic | Personal | Professional |
|---|---|---|---|
| Monthly upload | 60 MB | 10 GB | 20 GB |
| Max note size | 25 MB | 200 MB | 200 MB |
| Notebooks | 250 | 1,000 | 1,000 |
| Tags | 100,000 | 100,000 | 100,000 |
| Saved searches | 100 | 100 | 100 |
Instructions
Step 1: Quota Monitoring
// services/quota-service.js const Evernote = require('evernote'); class QuotaService { constructor(userStore) { this.userStore = userStore; } /** * Get current quota status */ async getQuotaStatus() { const user = await this.userStore.getUser(); const accounting = user.accounting; const uploadLimit = accounting.uploadLimit; const uploaded = accounting.uploaded; const remaining = uploadLimit - uploaded; const usagePercent = (uploaded / uploadLimit) * 100; return { tier: this.getTierName(user.privilege), uploadLimit: this.formatBytes(uploadLimit), uploaded: this.formatBytes(uploaded), remaining: this.formatBytes(remaining), usagePercent: usagePercent.toFixed(1) + '%', resetsAt: new Date(accounting.uploadLimitEnd), daysUntilReset: this.daysUntil(accounting.uploadLimitEnd), // Raw values for calculations raw: { uploadLimit, uploaded, remaining } }; } /** * Check if upload is safe */ async canUpload(fileSizeBytes) { const status = await this.getQuotaStatus(); return status.raw.remaining >= fileSizeBytes; } /** * Estimate uploads remaining */ async estimateRemainingUploads(avgFileSizeBytes) { const status = await this.getQuotaStatus(); return Math.floor(status.raw.remaining / avgFileSizeBytes); } /** * Check if approaching limit */ async isApproachingLimit(thresholdPercent = 80) { const status = await this.getQuotaStatus(); return parseFloat(status.usagePercent) >= thresholdPercent; } getTierName(privilege) { const tiers = { 1: 'Basic', 2: 'Personal (Premium)', 3: 'VIP', 5: 'Professional' }; return tiers[privilege] || 'Unknown'; } formatBytes(bytes) { if (!bytes) return '0 B'; const units = ['B', 'KB', 'MB', 'GB']; let i = 0; while (bytes >= 1024 && i < units.length - 1) { bytes /= 1024; i++; } return `${bytes.toFixed(2)} ${units[i]}`; } daysUntil(timestamp) { const ms = timestamp - Date.now(); return Math.max(0, Math.ceil(ms / (24 * 60 * 60 * 1000))); } } module.exports = QuotaService;
Step 2: Resource Optimization
// services/resource-optimizer.js const sharp = require('sharp'); const path = require('path'); class ResourceOptimizer { constructor(options = {}) { this.maxImageWidth = options.maxImageWidth || 1920; this.maxImageHeight = options.maxImageHeight || 1080; this.imageQuality = options.imageQuality || 80; this.maxFileSizeMB = options.maxFileSizeMB || 10; } /** * Optimize image before upload */ async optimizeImage(buffer, originalName) { const originalSize = buffer.length; // Determine format const ext = path.extname(originalName).toLowerCase(); const format = ext === '.png' ? 'png' : 'jpeg'; // Resize and compress let optimized = sharp(buffer) .resize(this.maxImageWidth, this.maxImageHeight, { fit: 'inside', withoutEnlargement: true }); if (format === 'jpeg') { optimized = optimized.jpeg({ quality: this.imageQuality }); } else { optimized = optimized.png({ compressionLevel: 9 }); } const result = await optimized.toBuffer(); const savings = originalSize - result.length; const savingsPercent = (savings / originalSize) * 100; console.log(`Image optimized: ${this.formatBytes(savings)} saved (${savingsPercent.toFixed(1)}%)`); return { buffer: result, originalSize, optimizedSize: result.length, savings, savingsPercent }; } /** * Estimate if file needs optimization */ shouldOptimize(fileSizeBytes, mimeType) { // Images over 500KB should be optimized if (mimeType.startsWith('image/') && fileSizeBytes > 500 * 1024) { return true; } // Files over max size must be optimized if (fileSizeBytes > this.maxFileSizeMB * 1024 * 1024) { return true; } return false; } /** * Compress PDF */ async compressPDF(buffer) { // Requires external tool like ghostscript // This is a placeholder - implement based on your needs return buffer; } formatBytes(bytes) { const units = ['B', 'KB', 'MB']; let i = 0; while (bytes >= 1024 && i < units.length - 1) { bytes /= 1024; i++; } return `${bytes.toFixed(2)} ${units[i]}`; } } module.exports = ResourceOptimizer;
Step 3: Efficient Note Creation
// services/efficient-note-service.js class EfficientNoteService { constructor(noteStore, quotaService, optimizer) { this.noteStore = noteStore; this.quota = quotaService; this.optimizer = optimizer; } /** * Create note with size checking */ async createNoteWithQuotaCheck(note, resources = []) { // Calculate total size let totalSize = Buffer.byteLength(note.content, 'utf8'); for (const resource of resources) { totalSize += resource.data.size; } // Check quota const canUpload = await this.quota.canUpload(totalSize); if (!canUpload) { const status = await this.quota.getQuotaStatus(); throw new Error( `Insufficient quota. Need ${this.formatBytes(totalSize)}, ` + `have ${status.remaining}. Resets in ${status.daysUntilReset} days.` ); } // Optimize resources if needed const optimizedResources = []; for (const resource of resources) { if (this.optimizer.shouldOptimize(resource.data.size, resource.mime)) { const optimized = await this.optimizer.optimizeImage( resource.data.body, resource.attributes?.fileName || 'image' ); resource.data.body = optimized.buffer; resource.data.size = optimized.buffer.length; resource.data.bodyHash = this.computeHash(optimized.buffer); } optimizedResources.push(resource); } note.resources = optimizedResources; return this.noteStore.createNote(note); } /** * Create note with deferred resources */ async createNoteDeferResources(title, content, resourcePaths) { // Create note without resources first const note = new Evernote.Types.Note(); note.title = title; note.content = this.wrapENML(content); const created = await this.noteStore.createNote(note); // Add resources in background for (const resourcePath of resourcePaths) { await this.addResourceToNote(created.guid, resourcePath); } return created; } /** * Batch small notes together */ async createNotesEfficiently(notes) { // Sort by size (smallest first) notes.sort((a, b) => { const sizeA = Buffer.byteLength(a.content, 'utf8'); const sizeB = Buffer.byteLength(b.content, 'utf8'); return sizeA - sizeB; }); const results = []; const status = await this.quota.getQuotaStatus(); let usedQuota = 0; for (const note of notes) { const noteSize = Buffer.byteLength(note.content, 'utf8'); if (usedQuota + noteSize > status.raw.remaining) { console.warn(`Quota limit reached. ${notes.length - results.length} notes skipped.`); break; } try { const result = await this.noteStore.createNote(note); results.push({ success: true, note: result }); usedQuota += noteSize; } catch (error) { results.push({ success: false, error: error.message }); } } return results; } computeHash(buffer) { const crypto = require('crypto'); return crypto.createHash('md5').update(buffer).digest(); } wrapENML(content) { return `<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE en-note SYSTEM "http://xml.evernote.com/pub/enml2.dtd"> <en-note>${content}</en-note>`; } formatBytes(bytes) { const units = ['B', 'KB', 'MB']; let i = 0; while (bytes >= 1024 && i < units.length - 1) { bytes /= 1024; i++; } return `${bytes.toFixed(2)} ${units[i]}`; } } module.exports = EfficientNoteService;
Step 4: Storage Cleanup
// services/storage-cleanup.js class StorageCleanup { constructor(noteStore) { this.noteStore = noteStore; } /** * Find large notes */ async findLargeNotes(thresholdMB = 5) { const filter = new Evernote.NoteStore.NoteFilter({ ascending: false, order: Evernote.Types.NoteSortOrder.SIZE }); const spec = new Evernote.NoteStore.NotesMetadataResultSpec({ includeTitle: true, includeContentLength: true, includeCreated: true, includeNotebookGuid: true }); const result = await this.noteStore.findNotesMetadata(filter, 0, 100, spec); const thresholdBytes = thresholdMB * 1024 * 1024; return result.notes.filter(note => note.contentLength > thresholdBytes ); } /** * Find duplicate notes (by title) */ async findPotentialDuplicates() { const spec = new Evernote.NoteStore.NotesMetadataResultSpec({ includeTitle: true, includeCreated: true, includeContentLength: true }); const filter = new Evernote.NoteStore.NoteFilter({}); const all = await this.noteStore.findNotesMetadata(filter, 0, 1000, spec); // Group by title const byTitle = {}; for (const note of all.notes) { const key = note.title.toLowerCase().trim(); if (!byTitle[key]) { byTitle[key] = []; } byTitle[key].push(note); } // Find duplicates const duplicates = []; for (const [title, notes] of Object.entries(byTitle)) { if (notes.length > 1) { duplicates.push({ title, count: notes.length, notes: notes.map(n => ({ guid: n.guid, created: new Date(n.created), size: this.formatBytes(n.contentLength) })) }); } } return duplicates; } /** * Find old, unmodified notes */ async findStaleNotes(daysOld = 365) { const cutoff = Date.now() - (daysOld * 24 * 60 * 60 * 1000); const filter = new Evernote.NoteStore.NoteFilter({ ascending: true, order: Evernote.Types.NoteSortOrder.UPDATED }); const spec = new Evernote.NoteStore.NotesMetadataResultSpec({ includeTitle: true, includeUpdated: true, includeContentLength: true }); const result = await this.noteStore.findNotesMetadata(filter, 0, 100, spec); return result.notes.filter(note => note.updated < cutoff); } /** * Calculate storage by notebook */ async getStorageByNotebook() { const notebooks = await this.noteStore.listNotebooks(); const storage = []; for (const notebook of notebooks) { const filter = new Evernote.NoteStore.NoteFilter({ notebookGuid: notebook.guid }); const spec = new Evernote.NoteStore.NotesMetadataResultSpec({ includeContentLength: true }); const result = await this.noteStore.findNotesMetadata(filter, 0, 1, spec); // Estimate total size (would need to paginate for accuracy) const avgSize = result.notes[0]?.contentLength || 0; const estimatedTotal = avgSize * result.totalNotes; storage.push({ name: notebook.name, guid: notebook.guid, noteCount: result.totalNotes, estimatedSize: this.formatBytes(estimatedTotal) }); } return storage.sort((a, b) => b.noteCount - a.noteCount); } formatBytes(bytes) { const units = ['B', 'KB', 'MB', 'GB']; let i = 0; while (bytes >= 1024 && i < units.length - 1) { bytes /= 1024; i++; } return `${bytes.toFixed(2)} ${units[i]}`; } } module.exports = StorageCleanup;
Step 5: Quota Alerts
// services/quota-alerts.js class QuotaAlertService { constructor(quotaService, options = {}) { this.quota = quotaService; this.thresholds = { warning: options.warningPercent || 70, critical: options.criticalPercent || 90 }; this.alertHandlers = []; } /** * Register alert handler */ onAlert(handler) { this.alertHandlers.push(handler); } /** * Check and alert */ async checkAndAlert() { const status = await this.quota.getQuotaStatus(); const usagePercent = parseFloat(status.usagePercent); if (usagePercent >= this.thresholds.critical) { await this.sendAlert('critical', status); } else if (usagePercent >= this.thresholds.warning) { await this.sendAlert('warning', status); } return status; } /** * Send alert to handlers */ async sendAlert(level, status) { const alert = { level, message: this.formatAlertMessage(level, status), status, timestamp: new Date() }; for (const handler of this.alertHandlers) { try { await handler(alert); } catch (error) { console.error('Alert handler error:', error); } } } formatAlertMessage(level, status) { const emoji = level === 'critical' ? '' : ''; return `${emoji} Evernote quota ${level.toUpperCase()}: ` + `${status.usagePercent} used (${status.uploaded} / ${status.uploadLimit}). ` + `Resets in ${status.daysUntilReset} days.`; } } // Usage example const alertService = new QuotaAlertService(quotaService); // Email alert alertService.onAlert(async (alert) => { await sendEmail({ to: 'admin@example.com', subject: `Evernote Quota ${alert.level}`, body: alert.message }); }); // Slack alert alertService.onAlert(async (alert) => { await slack.send({ channel: '#alerts', text: alert.message }); }); module.exports = QuotaAlertService;
Step 6: Usage Report
// scripts/quota-report.js async function generateQuotaReport(quotaService, cleanupService) { console.log('=== Evernote Quota Report ===\n'); // Current status const status = await quotaService.getQuotaStatus(); console.log('Account Tier:', status.tier); console.log('Upload Quota:', `${status.uploaded} / ${status.uploadLimit} (${status.usagePercent})`); console.log('Remaining:', status.remaining); console.log('Resets:', status.resetsAt.toLocaleDateString(), `(${status.daysUntilReset} days)`); // Large notes console.log('\n--- Large Notes (>5MB) ---'); const largeNotes = await cleanupService.findLargeNotes(5); if (largeNotes.length > 0) { largeNotes.forEach(note => { console.log(`- ${note.title}: ${cleanupService.formatBytes(note.contentLength)}`); }); console.log(`Total: ${largeNotes.length} notes`); } else { console.log('No large notes found'); } // Storage by notebook console.log('\n--- Storage by Notebook ---'); const storage = await cleanupService.getStorageByNotebook(); storage.slice(0, 10).forEach(nb => { console.log(`- ${nb.name}: ${nb.noteCount} notes (~${nb.estimatedSize})`); }); // Recommendations console.log('\n--- Recommendations ---'); const usagePercent = parseFloat(status.usagePercent); if (usagePercent > 80) { console.log('- Consider upgrading account tier'); console.log('- Optimize images before upload'); console.log('- Archive old notes to external storage'); } if (largeNotes.length > 10) { console.log('- Review and compress large notes'); console.log('- Consider moving attachments to cloud storage'); } console.log('\n=== End Report ==='); }
Output
- Quota monitoring service
- Image and resource optimization
- Efficient note creation with quota checking
- Storage cleanup utilities
- Alert system for quota thresholds
- Usage reporting
Cost Optimization Checklist
## Monthly Checklist - [ ] Check quota usage status - [ ] Review large notes (>5MB) - [ ] Find and merge duplicates - [ ] Archive stale notes - [ ] Optimize images in queue - [ ] Set up quota alerts
Resources
Next Steps
For architecture patterns, see
evernote-reference-architecture.