Process Evernote
You are converting Evernote notes into structured Obsidian markdown notes for the mind map vault.
You are converting Evernote notes into structured Obsidian markdown notes for the mind map vault.
Evernote's local storage is read directly — no API or export required. The pipeline:
1. **Prepare** a batch → extracts ENML content from Evernote's local Yjs storage and generates a Claude prompt
2. **Analyze** → you read the prompt and generate structured metadata JSON
3. **Apply** → creates Obsidian notes, renders web clip previews via Playwright, and copies attachments
## Quick Start
```bash
# Check status
source venv/bin/activate && python src/process_evernote.py status
# Prepare next batch (default 10 notes)
python src/process_evernote.py prepare 10
# After you generate the response JSON:
python src/process_evernote.py apply BATCH_NUM
Full Workflow
Step 1: Check Status
source venv/bin/activate && python src/process_evernote.py status
Shows: total notes, processed, remaining, notes with attachments, current batch number.
Step 2: Prepare a Batch
source venv/bin/activate && python src/process_evernote.py prepare [batch_size]
# or target a specific note by GUID:
python src/process_evernote.py prepare --guid <note-guid>
- Default batch size: 10 notes
- Creates
evernote_batch_N.json(note data + ENML + resources) - Creates
evernote_batch_N_prompt.txt(Claude analysis prompt with clean Markdown content)
Content extraction chain (highest → lowest fidelity):
- pycrdt — decodes Yjs
.datfile → raw ENML → clean Markdown via markdownify. Produces full article text with proper headings, links, and punctuation. - SQLite — reads
Offline_Search_Note_Content+Nodes_Note.source_URL. Clean text, no structure. - strings — legacy fallback for notes where both above fail. Noisy but workable.
The prompt labels the method: CONTENT [pycrdt], CONTENT [sqlite], or CONTENT [strings].
Note types the script handles:
- Web clips: Have
source_urlfrom ENML CSS metadata or SQLiteNodes_Note - Personal notes: User-written content, no source URL
Step 3: Analyze the Batch
Read evernote_batch_N_prompt.txt and generate structured metadata for each note.
For each note in the batch, produce:
{
"guid": "the-note-guid-from-prompt",
"title": "Concise, accurate title",
"source_name": "Publication name | null for personal notes",
"source_url": "https://article-url | null for personal notes",
"source_date": "YYYY-MM-DD",
"summary": "2-3 sentence summary with specific insights",
"key_points": [
"Specific, substantive point 1",
"Specific, substantive point 2",
"Specific, substantive point 3",
"Specific, substantive point 4",
"Specific, substantive point 5"
],
"tags": ["Topic", "Subtopic", "Tag"],
"authors": ["Author Name"],
"author_urls": {"Author Name": "https://profile-url | null"}
}
Analysis guidelines:
- For web clips: use the article content (now full clean Markdown) to determine real title, not Evernote title
- For personal notes: shorter key_points list is fine — capture the essence
source_date: prefer the article's publication date; fall back to theCreateddate shown in the prompt- Tags: use
Title-Case-With-Hyphensformat (e.g.,Mental-Health,AI,San-Francisco) author_urls: use null if no profile URL is apparent from the content- Content is now up to 6000 chars of clean Markdown — use it fully
Save the complete JSON array as evernote_batch_N_response.json.
Step 4: Apply
source venv/bin/activate && python src/process_evernote.py apply N
# or with explicit response file:
python src/process_evernote.py apply N evernote_batch_N_response.json
This will:
- Create Obsidian markdown notes in
Lifegames/ - Render a full-length PNG preview of each web clip via Playwright/Chromium:
- ENML HTML with base64-embedded images from resource-cache
- Source Serif 4 (Google Fonts) mapped to
source-serif-pro— Medium's actual body font - CSS quote escaping fix (pycrdt outputs unescaped
"in style attributes) - Content height auto-detected via JS getBoundingClientRect
- 748px logical width @ 2x device pixel ratio
- Copy all attachment images to
Lifegames/Attachments/with hash-based filenames - Update
evernote_progress.jsonwith processed GUIDs
Step 5: Repeat
Continue until all notes are processed:
python src/process_evernote.py status
python src/process_evernote.py prepare 10
# ... analyze ... apply ...
Generated Note Format
Web Clip (with source URL)
---
title: "Article Title"
source: "Publication Name"
source_url: "https://..."
date: YYYY-MM-DD
authors:
- Author Name
tags: ["Topic", "Tag"]
filename: "8charhash"
cssclass: article-note
file_hash: "8charhash"
evernote_guid: "full-guid"
evernote_notebook: "Personal"
generated_by: "claude-code"
generated_model: "claude-opus-4-6"
generated_at: "YYYY-MM-DD"
---
# Article Title
> [!info] Article Information
> - **Source**: [Publication](url)
> - **Date**: YYYY-MM-DD
> - **Authors**: [Name](url)
## Summary
...
## Key Points
- ...
## Author Information
- [Author](url)
## Linked Concepts
<!-- For manual wiki-linking later -->
## Notes
## Attachments
- ![[hash_preview.png]]
Personal Note (no source URL)
---
title: "Note Title"
date: YYYY-MM-DD
tags: ["Tag"]
filename: "8charhash"
evernote_guid: "full-guid"
...
---
# Note Title
## Summary
...
## Key Points
- ...
## Linked Concepts
<!-- For manual wiki-linking later -->
## Notes
## Attachments
- ![[hash_preview.png]]
File Naming
- Preview:
{8charhash}_preview.png— full-length Playwright screenshot of original ENML - Resources:
{8charhash}.jpg(primary),{8charhash}_1.jpg,{8charhash}_2.jpg(additional) - Note hash: MD5 of the note GUID (consistent, reproducible across runs)
- Notes: sanitized title (removes
[]#^|\/:?*<>", max 100 chars)
Evernote Storage Locations (read-only)
- ENML content: `~/Library/Containers/co
Maintain Process Evernote?
Let people know it's listed here — add the badge (live metrics, light/dark aware) or a plain link to your README or docs.
[Process Evernote on getagentictools](https://getagentictools.com/loops/j0nathan-ll0yd-check-status?ref=badge)