evernote-core-workflow-b
Execute Evernote secondary workflow: Search and Retrieval. Use when implementing search features, finding notes, filtering content, or building search interfaces. Trigger with phrases like "search evernote", "find evernote notes", "evernote search", "query evernote". allowed-tools: Read, Write, Edit, Bash(npm:*), Grep version: 1.0.0 license: MIT author: Jeremy Longshore <jeremy@intentsolutions.io>
Allowed Tools
No tools specified
Provided by Plugin
evernote-pack
Claude Code skill pack for Evernote (24 skills)
Installation
This skill is included in the evernote-pack plugin:
/plugin install evernote-pack@claude-code-plugins-plus
Click to copy
Instructions
# Evernote Core Workflow B: Search & Retrieval
## Overview
Comprehensive search and retrieval workflow for Evernote, including search grammar, filters, pagination, and related notes discovery.
## Prerequisites
- Completed `evernote-install-auth` setup
- Understanding of Evernote search grammar
- Valid access token configured
## Instructions
### Step 1: Search Service Foundation
```javascript
// services/search-service.js
const Evernote = require('evernote');
class SearchService {
constructor(noteStore) {
this.noteStore = noteStore;
}
/**
* Basic text search across all notes
*/
async search(query, options = {}) {
const {
maxResults = 50,
offset = 0,
sortOrder = 'UPDATED',
ascending = false
} = options;
const filter = new Evernote.NoteStore.NoteFilter({
words: query,
ascending,
order: Evernote.Types.NoteSortOrder[sortOrder]
});
const spec = new Evernote.NoteStore.NotesMetadataResultSpec({
includeTitle: true,
includeContentLength: true,
includeCreated: true,
includeUpdated: true,
includeTagGuids: true,
includeNotebookGuid: true,
includeAttributes: true
});
const result = await this.noteStore.findNotesMetadata(
filter,
offset,
maxResults,
spec
);
return {
notes: result.notes,
totalNotes: result.totalNotes,
hasMore: offset + result.notes.length < result.totalNotes
};
}
/**
* Search within a specific notebook
*/
async searchInNotebook(notebookGuid, query, maxResults = 50) {
const filter = new Evernote.NoteStore.NoteFilter({
notebookGuid,
words: query
});
const spec = this.getDefaultResultSpec();
return this.noteStore.findNotesMetadata(filter, 0, maxResults, spec);
}
/**
* Search by tags (AND logic)
*/
async searchByTags(tagGuids, maxResults = 50) {
const filter = new Evernote.NoteStore.NoteFilter({
tagGuids,
ascending: false,
order: Evernote.Types.NoteSortOrder.UPDATED
});
const spec = this.getDefaultResultSpec();
return this.noteStore.findNotesMetadata(filter, 0, maxResults, spec);
}
getDefaultResultSpec() {
return new Evernote.NoteStore.NotesMetadataResultSpec({
includeTitle: true,
includeCreated: true,
includeUpdated: true,
includeTagGuids: true,
includeNotebookGuid: true
});
}
}
module.exports = SearchService;
```
### Step 2: Advanced Search Grammar Builder
```javascript
class QueryBuilder {
constructor() {
this.parts = [];
}
/**
* Search in specific notebook
*/
notebook(name) {
this.parts.push(`notebook:"${name}"`);
return this;
}
/**
* Search for notes with tag
*/
tag(name) {
this.parts.push(`tag:"${name}"`);
return this;
}
/**
* Exclude notes with tag
*/
excludeTag(name) {
this.parts.push(`-tag:"${name}"`);
return this;
}
/**
* Search in note title
*/
intitle(text) {
this.parts.push(`intitle:"${text}"`);
return this;
}
/**
* Created on or after date
*/
createdAfter(date) {
this.parts.push(`created:${this.formatDate(date)}`);
return this;
}
/**
* Created before date
*/
createdBefore(date) {
this.parts.push(`-created:${this.formatDate(date)}`);
return this;
}
/**
* Updated on or after date
*/
updatedAfter(date) {
this.parts.push(`updated:${this.formatDate(date)}`);
return this;
}
/**
* Notes with uncompleted todos
*/
hasUncompletedTodos() {
this.parts.push('todo:false');
return this;
}
/**
* Notes with completed todos
*/
hasCompletedTodos() {
this.parts.push('todo:true');
return this;
}
/**
* Notes with any todos
*/
hasTodos() {
this.parts.push('todo:*');
return this;
}
/**
* Notes with attachments
*/
hasAttachments() {
this.parts.push('resource:*');
return this;
}
/**
* Notes with images
*/
hasImages() {
this.parts.push('resource:image/*');
return this;
}
/**
* Notes with PDFs
*/
hasPDFs() {
this.parts.push('resource:application/pdf');
return this;
}
/**
* Notes with encryption
*/
hasEncryption() {
this.parts.push('encryption:');
return this;
}
/**
* Author filter
*/
author(name) {
this.parts.push(`author:"${name}"`);
return this;
}
/**
* Source filter (web clip, email, etc.)
*/
source(type) {
this.parts.push(`source:${type}`);
return this;
}
/**
* Free text search
*/
text(query) {
this.parts.push(query);
return this;
}
/**
* Exact phrase search
*/
phrase(text) {
this.parts.push(`"${text}"`);
return this;
}
/**
* Wildcard search (suffix only)
*/
wildcard(prefix) {
this.parts.push(`${prefix}*`);
return this;
}
/**
* Negate next condition
*/
not() {
this._negate = true;
return this;
}
/**
* Match ANY condition (default is ALL)
*/
any() {
this._any = true;
return this;
}
/**
* Relative date helpers
*/
today() {
return this.createdAfter('day');
}
thisWeek() {
return this.createdAfter('week');
}
thisMonth() {
return this.createdAfter('month');
}
lastNDays(n) {
this.parts.push(`created:day-${n}`);
return this;
}
formatDate(input) {
if (typeof input === 'string') {
// Relative dates like 'day', 'week', 'month', 'year'
return input;
}
// Absolute date: YYYYMMDD
return input.toISOString().slice(0, 10).replace(/-/g, '');
}
/**
* Build final query string
*/
build() {
let query = this.parts.join(' ');
if (this._any) {
query = 'any: ' + query;
}
return query;
}
/**
* Reset builder
*/
reset() {
this.parts = [];
this._any = false;
this._negate = false;
return this;
}
}
module.exports = QueryBuilder;
```
### Step 3: Paginated Search Results
```javascript
class SearchService {
// ... previous methods ...
/**
* Paginated search with cursor-based iteration
*/
async *paginatedSearch(query, pageSize = 50) {
const filter = new Evernote.NoteStore.NoteFilter({
words: query,
ascending: false,
order: Evernote.Types.NoteSortOrder.UPDATED
});
const spec = this.getDefaultResultSpec();
let offset = 0;
let hasMore = true;
while (hasMore) {
const result = await this.noteStore.findNotesMetadata(
filter,
offset,
pageSize,
spec
);
for (const note of result.notes) {
yield note;
}
offset += result.notes.length;
hasMore = offset < result.totalNotes;
}
}
/**
* Get all matching notes (careful with large result sets)
*/
async getAllMatching(query, maxTotal = 1000) {
const allNotes = [];
const pageSize = 100;
for await (const note of this.paginatedSearch(query, pageSize)) {
allNotes.push(note);
if (allNotes.length >= maxTotal) break;
}
return allNotes;
}
/**
* Search with full note content (slower, use sparingly)
*/
async searchWithContent(query, maxResults = 20) {
const metadata = await this.search(query, { maxResults });
const notesWithContent = await Promise.all(
metadata.notes.map(async note => {
const fullNote = await this.noteStore.getNote(
note.guid,
true, // withContent
false, // withResourcesData
false, // withResourcesRecognition
false // withResourcesAlternateData
);
return fullNote;
})
);
return notesWithContent;
}
}
```
### Step 4: Related Notes Discovery
```javascript
class SearchService {
// ... previous methods ...
/**
* Find notes related to a given note
*/
async findRelatedNotes(noteGuid) {
const relatedResult = await this.noteStore.findRelated(
new Evernote.NoteStore.RelatedQuery({
noteGuid
}),
new Evernote.NoteStore.RelatedResultSpec({
maxNotes: 10,
maxNotebooks: 3,
maxTags: 10
})
);
return {
notes: relatedResult.notes || [],
notebooks: relatedResult.notebooks || [],
tags: relatedResult.tags || []
};
}
/**
* Find notes related to text content
*/
async findRelatedToText(text) {
const relatedResult = await this.noteStore.findRelated(
new Evernote.NoteStore.RelatedQuery({
plainText: text
}),
new Evernote.NoteStore.RelatedResultSpec({
maxNotes: 10
})
);
return relatedResult.notes || [];
}
/**
* Get notes in same notebook
*/
async getNotebookNotes(notebookGuid, maxResults = 50) {
const filter = new Evernote.NoteStore.NoteFilter({
notebookGuid,
ascending: false,
order: Evernote.Types.NoteSortOrder.UPDATED
});
const spec = this.getDefaultResultSpec();
return this.noteStore.findNotesMetadata(filter, 0, maxResults, spec);
}
/**
* Get recent notes
*/
async getRecentNotes(limit = 20) {
const filter = new Evernote.NoteStore.NoteFilter({
ascending: false,
order: Evernote.Types.NoteSortOrder.UPDATED
});
const spec = this.getDefaultResultSpec();
return this.noteStore.findNotesMetadata(filter, 0, limit, spec);
}
}
```
### Step 5: Search Result Enrichment
```javascript
class SearchService {
// ... previous methods ...
/**
* Enrich search results with notebook and tag names
*/
async enrichResults(searchResult) {
// Cache lookups
if (!this._notebookCache) {
const notebooks = await this.noteStore.listNotebooks();
this._notebookCache = new Map(
notebooks.map(nb => [nb.guid, nb])
);
}
if (!this._tagCache) {
const tags = await this.noteStore.listTags();
this._tagCache = new Map(
tags.map(t => [t.guid, t])
);
}
return searchResult.notes.map(note => ({
guid: note.guid,
title: note.title,
created: new Date(note.created),
updated: new Date(note.updated),
notebookName: this._notebookCache.get(note.notebookGuid)?.name,
tags: (note.tagGuids || []).map(
guid => this._tagCache.get(guid)?.name
).filter(Boolean),
contentLength: note.contentLength
}));
}
/**
* Search with enriched results
*/
async searchEnriched(query, options = {}) {
const result = await this.search(query, options);
const enrichedNotes = await this.enrichResults(result);
return {
notes: enrichedNotes,
totalNotes: result.totalNotes,
hasMore: result.hasMore
};
}
}
```
### Step 6: Complete Search Workflow Example
```javascript
// example-search-workflow.js
const Evernote = require('evernote');
const SearchService = require('./services/search-service');
const QueryBuilder = require('./utils/query-builder');
async function searchWorkflow(accessToken) {
const client = new Evernote.Client({
token: accessToken,
sandbox: true
});
const noteStore = client.getNoteStore();
const search = new SearchService(noteStore);
const qb = new QueryBuilder();
// Example 1: Find uncompleted todos from this week
const todoQuery = qb
.thisWeek()
.hasUncompletedTodos()
.build();
console.log('Query:', todoQuery);
const todos = await search.searchEnriched(todoQuery);
console.log(`Found ${todos.totalNotes} notes with uncompleted todos`);
// Example 2: Find meeting notes in Work notebook
qb.reset();
const meetingQuery = qb
.notebook('Work')
.intitle('meeting')
.lastNDays(30)
.build();
const meetings = await search.searchEnriched(meetingQuery);
console.log(`Found ${meetings.totalNotes} meeting notes`);
// Example 3: Find notes with attachments
qb.reset();
const attachmentQuery = qb
.hasAttachments()
.updatedAfter('month')
.build();
const withAttachments = await search.search(attachmentQuery);
console.log(`Found ${withAttachments.totalNotes} notes with attachments`);
// Example 4: Complex query - urgent tasks not in Archive
qb.reset();
const urgentQuery = qb
.tag('urgent')
.excludeTag('archived')
.hasUncompletedTodos()
.build();
const urgent = await search.searchEnriched(urgentQuery);
console.log('Urgent tasks:');
urgent.notes.forEach(note => {
console.log(` - ${note.title} (${note.notebookName})`);
});
// Example 5: Find related notes
if (urgent.notes.length > 0) {
const related = await search.findRelatedNotes(urgent.notes[0].guid);
console.log(`Related notes to "${urgent.notes[0].title}":`);
related.notes.forEach(note => {
console.log(` - ${note.title}`);
});
}
return {
todos,
meetings,
withAttachments,
urgent
};
}
module.exports = searchWorkflow;
```
## Output
- Flexible search service with query builder
- Search grammar support for complex queries
- Paginated results for large datasets
- Related notes discovery
- Enriched results with notebook/tag names
## Search Grammar Quick Reference
| Operator | Example | Description |
|----------|---------|-------------|
| `notebook:` | `notebook:"Work"` | Restrict to notebook |
| `tag:` | `tag:urgent` | Has tag |
| `-tag:` | `-tag:archived` | Exclude tag |
| `intitle:` | `intitle:meeting` | Word in title |
| `created:` | `created:day-7` | Created after |
| `updated:` | `updated:week` | Updated after |
| `todo:` | `todo:false` | Has uncompleted todos |
| `resource:` | `resource:image/*` | Has attachment type |
| `any:` | `any: term1 term2` | Match ANY term |
| `"phrase"` | `"exact match"` | Exact phrase |
| `prefix*` | `meet*` | Wildcard suffix |
## Error Handling
| Error | Cause | Solution |
|-------|-------|----------|
| `RATE_LIMIT_REACHED` | Too many searches | Add delay between requests |
| `INVALID_SEARCH` | Bad search grammar | Validate query syntax |
| `QUOTA_REACHED` | Search quota exceeded | Reduce search frequency |
## Resources
- [Search Grammar](https://dev.evernote.com/doc/articles/search_grammar.php)
- [Search Overview](https://dev.evernote.com/doc/articles/search.php)
- [Related Notes](https://dev.evernote.com/doc/articles/related_notes.php)
## Next Steps
For error handling patterns, see `evernote-common-errors`.