environment-setup-guide
Guide developers through setting up development environments with proper tools, dependencies, and configurations
evernote-performance-tuning
Optimize Evernote integration performance. Use when improving response times, reducing API calls, or scaling Evernote integrations. Trigger with phrases like "evernote performance", "optimize evernote", "evernote speed", "evernote caching".
Full skill instructions
Optimize Evernote API integration performance through caching, efficient API usage, connection pooling, and smart data retrieval strategies.
// services/cache-service.js
const Redis = require('ioredis');
class EvernoteCacheService {
constructor(redisUrl) {
this.redis = new Redis(redisUrl);
this.defaultTTL = 300; // 5 minutes
}
/**
* Cache key generators
*/
keys = {
notebooks: (userId) => `evernote:${userId}:notebooks`,
tags: (userId) => `evernote:${userId}:tags`,
note: (guid) => `evernote:note:${guid}`,
noteMetadata: (guid) => `evernote:note:${guid}:meta`,
search: (userId, query) => `evernote:${userId}:search:${this.hashQuery(query)}`,
syncState: (userId) => `evernote:${userId}:syncState`
};
hashQuery(query) {
const crypto = require('crypto');
return crypto.createHash('md5').update(query).digest('hex');
}
/**
* Cache notebooks (longer TTL - rarely change)
*/
async cacheNotebooks(userId, notebooks) {
const key = this.keys.notebooks(userId);
await this.redis.setex(key, 3600, JSON.stringify(notebooks)); // 1 hour
}
async getNotebooks(userId) {
const key = this.keys.notebooks(userId);
const cached = await this.redis.get(key);
return cached ? JSON.parse(cached) : null;
}
/**
* Cache note metadata (shorter TTL)
*/
async cacheNoteMetadata(guid, metadata) {
const key = this.keys.noteMetadata(guid);
await this.redis.setex(key, this.defaultTTL, JSON.stringify(metadata));
}
async getNoteMetadata(guid) {
const key = this.keys.noteMetadata(guid);
const cached = await this.redis.get(key);
return cached ? JSON.parse(cached) : null;
}
/**
* Cache full note content (configurable TTL)
*/
async cacheNote(guid, note, ttl = 300) {
const key = this.keys.note(guid);
await this.redis.setex(key, ttl, JSON.stringify(note));
}
async getNote(guid) {
const key = this.keys.note(guid);
const cached = await this.redis.get(key);
return cached ? JSON.parse(cached) : null;
}
/**
* Cache search results (short TTL)
*/
async cacheSearch(userId, query, results) {
const key = this.keys.search(userId, query);
await this.redis.setex(key, 60, JSON.stringify(results)); // 1 minute
}
async getSearch(userId, query) {
const key = this.keys.search(userId, query);
const cached = await this.redis.get(key);
return cached ? JSON.parse(cached) : null;
}
/**
* Invalidate cache on changes
*/
async invalidateNote(guid) {
await this.redis.del(this.keys.note(guid));
await this.redis.del(this.keys.noteMetadata(guid));
}
async invalidateUserCache(userId) {
const pattern = `evernote:${userId}:*`;
const keys = await this.redis.keys(pattern);
if (keys.length > 0) {
await this.redis.del(...keys);
}
}
}
module.exports = EvernoteCacheService;
// services/cached-evernote-client.js
const Evernote = require('evernote');
const EvernoteCacheService = require('./cache-service');
class CachedEvernoteClient {
constructor(accessToken, userId, cacheService) {
this.client = new Evernote.Client({
token: accessToken,
sandbox: process.env.EVERNOTE_SANDBOX === 'true'
});
this.noteStore = this.client.getNoteStore();
this.userId = userId;
this.cache = cacheService;
}
/**
* List notebooks with caching
*/
async listNotebooks(forceRefresh = false) {
if (!forceRefresh) {
const cached = await this.cache.getNotebooks(this.userId);
if (cached) {
console.log('Cache HIT: notebooks');
return cached;
}
}
console.log('Cache MISS: notebooks');
const notebooks = await this.noteStore.listNotebooks();
await this.cache.cacheNotebooks(this.userId, notebooks);
return notebooks;
}
/**
* Get note with caching
*/
async getNote(guid, options = {}) {
const {
withContent = true,
withResources = false,
forceRefresh = false
} = options;
// Only cache if requesting content without resources
const canCache = withContent && !withResources;
if (canCache && !forceRefresh) {
const cached = await this.cache.getNote(guid);
if (cached) {
console.log('Cache HIT: note', guid);
return cached;
}
}
console.log('Cache MISS: note', guid);
const note = await this.noteStore.getNote(
guid,
withContent,
withResources,
false,
false
);
if (canCache) {
await this.cache.cacheNote(guid, note);
}
return note;
}
/**
* Search with caching
*/
async search(query, options = {}) {
const { maxResults = 50, forceRefresh = false } = options;
if (!forceRefresh) {
const cached = await this.cache.getSearch(this.userId, query);
if (cached) {
console.log('Cache HIT: search', query);
return cached;
}
}
console.log('Cache MISS: search', query);
const filter = new Evernote.NoteStore.NoteFilter({ words: query });
const spec = new Evernote.NoteStore.NotesMetadataResultSpec({
includeTitle: true,
includeCreated: true,
includeUpdated: true,
includeTagGuids: true,
includeNotebookGuid: true
});
const results = await this.noteStore.findNotesMetadata(
filter,
0,
maxResults,
spec
);
await this.cache.cacheSearch(this.userId, query, results);
return results;
}
}
module.exports = CachedEvernoteClient;
// utils/request-batcher.js
class RequestBatcher {
constructor(options = {}) {
this.batchSize = options.batchSize || 10;
this.batchDelay = options.batchDelay || 100;
this.queue = [];
this.processing = false;
}
/**
* Add request to batch
*/
async add(operation) {
return new Promise((resolve, reject) => {
this.queue.push({ operation, resolve, reject });
if (!this.processing) {
this.processBatch();
}
});
}
/**
* Process queued requests
*/
async processBatch() {
if (this.queue.length === 0) {
this.processing = false;
return;
}
this.processing = true;
// Take batch from queue
const batch = this.queue.splice(0, this.batchSize);
// Execute in parallel
await Promise.all(
batch.map(async ({ operation, resolve, reject }) => {
try {
const result = await operation();
resolve(result);
} catch (error) {
reject(error);
}
})
);
// Small delay before next batch
if (this.queue.length > 0) {
await new Promise(r => setTimeout(r, this.batchDelay));
this.processBatch();
} else {
this.processing = false;
}
}
}
module.exports = RequestBatcher;
// services/optimized-note-service.js
class OptimizedNoteService {
constructor(noteStore, cache) {
this.noteStore = noteStore;
this.cache = cache;
}
/**
* Get minimal data needed (don't over-fetch)
*/
async getNotePreview(guid) {
// Only get metadata, not full content
return this.noteStore.getNote(guid, false, false, false, false);
}
/**
* Get notes with content in single request
*/
async getNotesWithContent(guids) {
// Evernote doesn't support batch note retrieval
// Optimize by parallelizing with rate limit awareness
const results = [];
const batchSize = 5;
for (let i = 0; i < guids.length; i += batchSize) {
const batch = guids.slice(i, i + batchSize);
const batchResults = await Promise.all(
batch.map(guid =>
this.noteStore.getNote(guid, true, false, false, false)
)
);
results.push(...batchResults);
// Small delay between batches
if (i + batchSize < guids.length) {
await new Promise(r => setTimeout(r, 100));
}
}
return results;
}
/**
* Get resources separately (more efficient for large files)
*/
async getNoteWithLazyResources(guid) {
// Get note without resource data
const note = await this.noteStore.getNote(guid, true, false, false, false);
// Create lazy loader for resources
note.loadResource = async (resourceGuid) => {
return this.noteStore.getResource(
resourceGuid,
true, // withData
false, // withRecognition
false, // withAttributes
false // withAlternateData
);
};
return note;
}
/**
* Efficient search with pagination
*/
async* searchPaginated(query, pageSize = 50) {
const filter = new Evernote.NoteStore.NoteFilter({ words: query });
const spec = new Evernote.NoteStore.NotesMetadataResultSpec({
includeTitle: true,
includeUpdated: true
});
let offset = 0;
let total = null;
while (total === null || offset < total) {
const result = await this.noteStore.findNotesMetadata(
filter,
offset,
pageSize,
spec
);
total = result.totalNotes;
for (const note of result.notes) {
yield note;
}
offset += result.notes.length;
}
}
}
module.exports = OptimizedNoteService;
// utils/connection-manager.js
class ConnectionManager {
constructor() {
this.clients = new Map();
this.maxIdleTime = 5 * 60 * 1000; // 5 minutes
}
/**
* Get or create client for user
*/
getClient(userId, accessToken) {
const existing = this.clients.get(userId);
if (existing) {
existing.lastUsed = Date.now();
return existing.client;
}
const client = new Evernote.Client({
token: accessToken,
sandbox: process.env.EVERNOTE_SANDBOX === 'true'
});
this.clients.set(userId, {
client,
lastUsed: Date.now()
});
return client;
}
/**
* Clean up idle connections
*/
cleanup() {
const now = Date.now();
for (const [userId, data] of this.clients) {
if (now - data.lastUsed > this.maxIdleTime) {
this.clients.delete(userId);
console.log(`Cleaned up idle connection for user ${userId}`);
}
}
}
/**
* Start cleanup interval
*/
startCleanup(interval = 60000) {
setInterval(() => this.cleanup(), interval);
}
}
module.exports = ConnectionManager;
// utils/performance-monitor.js
class PerformanceMonitor {
constructor() {
this.metrics = {
apiCalls: 0,
cacheHits: 0,
cacheMisses: 0,
totalLatency: 0,
errors: 0
};
this.callDurations = [];
}
/**
* Track API call
*/
trackCall(operation, duration, fromCache = false) {
this.metrics.apiCalls++;
this.metrics.totalLatency += duration;
this.callDurations.push({ operation, duration, fromCache, timestamp: Date.now() });
if (fromCache) {
this.metrics.cacheHits++;
} else {
this.metrics.cacheMisses++;
}
// Keep last 1000 calls
if (this.callDurations.length > 1000) {
this.callDurations.shift();
}
}
/**
* Track error
*/
trackError(operation, error) {
this.metrics.errors++;
console.error(`[PERF] Error in ${operation}:`, error.message);
}
/**
* Get performance stats
*/
getStats() {
const avgLatency = this.metrics.apiCalls > 0
? this.metrics.totalLatency / this.metrics.apiCalls
: 0;
const cacheHitRate = this.metrics.apiCalls > 0
? (this.metrics.cacheHits / this.metrics.apiCalls) * 100
: 0;
// Calculate p95 latency
const sortedDurations = [...this.callDurations]
.map(c => c.duration)
.sort((a, b) => a - b);
const p95Index = Math.floor(sortedDurations.length * 0.95);
const p95Latency = sortedDurations[p95Index] || 0;
return {
totalCalls: this.metrics.apiCalls,
cacheHits: this.metrics.cacheHits,
cacheMisses: this.metrics.cacheMisses,
cacheHitRate: `${cacheHitRate.toFixed(1)}%`,
avgLatencyMs: avgLatency.toFixed(2),
p95LatencyMs: p95Latency.toFixed(2),
errors: this.metrics.errors
};
}
/**
* Create instrumented wrapper
*/
instrument(noteStore) {
const monitor = this;
return new Proxy(noteStore, {
get(target, prop) {
const original = target[prop];
if (typeof original !== 'function') {
return original;
}
return async (...args) => {
const start = Date.now();
try {
const result = await original.apply(target, args);
const duration = Date.now() - start;
monitor.trackCall(prop, duration);
return result;
} catch (error) {
const duration = Date.now() - start;
monitor.trackCall(prop, duration);
monitor.trackError(prop, error);
throw error;
}
};
}
});
}
}
module.exports = PerformanceMonitor;
// example-optimized.js
const Redis = require('ioredis');
const EvernoteCacheService = require('./services/cache-service');
const CachedEvernoteClient = require('./services/cached-evernote-client');
const PerformanceMonitor = require('./utils/performance-monitor');
async function main() {
const redis = new Redis(process.env.REDIS_URL);
const cache = new EvernoteCacheService(redis);
const monitor = new PerformanceMonitor();
const userId = 'user-123';
const client = new CachedEvernoteClient(
process.env.EVERNOTE_ACCESS_TOKEN,
userId,
cache
);
// Instrument for monitoring
client.noteStore = monitor.instrument(client.noteStore);
// First call - cache miss
console.log('\nFirst request (cache miss):');
console.time('First notebooks call');
await client.listNotebooks();
console.timeEnd('First notebooks call');
// Second call - cache hit
console.log('\nSecond request (cache hit):');
console.time('Second notebooks call');
await client.listNotebooks();
console.timeEnd('Second notebooks call');
// Search with caching
console.log('\nSearch (cache miss):');
console.time('First search');
await client.search('meeting notes');
console.timeEnd('First search');
console.log('\nSearch (cache hit):');
console.time('Second search');
await client.search('meeting notes');
console.timeEnd('Second search');
// Print stats
console.log('\nPerformance Stats:', monitor.getStats());
redis.quit();
}
main().catch(console.error);
| Optimization | Impact | When to Use |
|---|---|---|
| Cache notebooks | High | Always (rarely change) |
| Cache search results | Medium | Repeated searches |
| Lazy load resources | High | Large attachments |
| Request batching | Medium | Bulk operations |
| Skip content flag | High | Listing notes |
For cost optimization, see evernote-cost-tuning.
Guide developers through setting up development environments with proper tools, dependencies, and configurations
Implement Exa lint rules, policy enforcement, and automated guardrails. Use when setting up code quality rules for Exa integrations, implementing pre-commit hooks, or configuring CI policy checks for Exa best practices. Trigger with phrases like "exa policy", "exa lint", "exa guardrails", "exa be...
Configure CI/CD pipelines for Documenso integrations. Use when setting up automated testing, deployment pipelines, or continuous integration for Documenso projects. Trigger with phrases like "documenso CI", "documenso GitHub Actions", "documenso pipeline", "documenso automated testing".
Build check model availability and implement fallback chains. Use when building resilient systems or handling model outages. Trigger with phrases like 'openrouter availability', 'openrouter fallback', 'openrouter model down', 'openrouter health check'.
Configure Langfuse enterprise organization management and access control. Use when implementing team access controls, configuring organization settings, or setting up role-based permissions for Langfuse projects. Trigger with phrases like "langfuse RBAC", "langfuse teams", "langfuse organization"...
Execute compliance and security auditing for Cursor usage. Triggers on "cursor compliance", "cursor audit", "cursor security review", "cursor soc2", "cursor gdpr". Use when analyzing or auditing cursor compliance audit. Trigger with phrases like "cursor compliance audit", "cursor audit", "cursor".
Configure Apollo.io CI/CD integration. Use when setting up automated testing, continuous integration, or deployment pipelines for Apollo integrations. Trigger with phrases like "apollo ci", "apollo github actions", "apollo pipeline", "apollo ci/cd", "apollo automated tests".
Create robust Python automation with full logging and safety checks. Use when tasks need complex data processing, authenticated API work, conditional file operations, or error handling beyond simple shell commands.
Configure PostHog across development, staging, and production environments. Use when setting up multi-environment deployments, configuring per-environment secrets, or implementing environment-specific PostHog configurations. Trigger with phrases like "posthog environments", "posthog staging", "po...
Expert in designing and building autonomous AI agents. Masters tool use, memory systems, planning strategies, and multi-agent orchestration. Use when: build agent, AI agent, autonomous agent, tool use, function calling.
Validate insecure deserialization checker operations. Auto-activating skill for Security Fundamentals. Triggers on: insecure deserialization checker, insecure deserialization checker Part of the Security Fundamentals skill category. Use when working with insecure deserialization checker functiona...
Manage path traversal finder operations. Auto-activating skill for Security Fundamentals. Triggers on: path traversal finder, path traversal finder Part of the Security Fundamentals skill category. Use when working with path traversal finder functionality. Trigger with phrases like "path traversa...
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience