Importing from Evernote¶
When to use¶
When a user asks to import an Evernote export (.enex). The Import tab hands the task
straight to you: you own it end to end — run the import, then make the result feel
native and resolve whatever couldn't be converted, asking the user when you need a
decision. Read limestone-vault-conventions and working-with-collections first.
Run the import (one tool call)¶
Call limestone_import_notebook(path, collection_name). It runs the deterministic
importer (below) — the whole bulk conversion at once — and returns a summary:
collection, notes_imported, attachments_imported, attachments_skipped, and an
unconverted list. Never import notes one at a time yourself; that's what this
tool is for. Then finalize (see below).
How the import works (what the tool does)¶
An .enex is one XML file of many <note>s, each with metadata, an ENML
<content> body, and base64 <resource> attachments referenced inline by
<en-media hash=…> (the hash is the MD5 of the resource's bytes). The importer
streams the file (bounded memory, whatever its size) and writes:
| Evernote | Limestone |
|---|---|
the notebook (the .enex) |
one collection (named for the file) |
a <note> |
a record (row + Markdown note); duplicate titles get a numeric suffix so their folders don't collide |
<title> |
the record title |
<created> / <updated> (20211210T120811Z) |
Created / Updated date properties, ISO-8601 (2021-12-10T12:08:11+00:00) |
<note-attributes><author> |
Author text property |
<note-attributes><source-url> |
Source URL text property |
ENML <h3> / <div> / <ul> / <b> / <a> … |
Markdown headings / paragraphs / lists / marks / links |
ENML <table> |
a limestone-table fenced block (see below) |
<en-media> → its <resource> |
the file, stored beside the note, embedded inline as ![[filename]] (images render; other files show as a clickable chip) |
The limestone-table fence¶
A table is a fenced block whose body is JSON — exactly what the editor's serializer
(apps/web/src/editor/markdown/serializer.ts) reads:
```limestone-table
{"headers": [], "rows": [["**Reagent**", "**Vol**"], ["Glucose", "20 ml"]]}
```
Evernote tables have no header row, so headers is [] and every row is data; each
cell is inline Markdown (bold/links survive).
Finalizing an import (your job)¶
After a run you get an ImportResult with counts and an unconverted list. Make
the collection feel native:
- Retype properties.
Authorimports astext; if the vault has matching members, consider auserproperty. ConfirmCreated/Updatedrender as dates. - Split or rename the collection if one notebook clearly holds several distinct record types (e.g. protocols vs. results) — create the extra collections and move rows. Ask the user before large restructuring.
- Resolve
unconverteditems. Each names a note and a reason: attachment … Unsupported file type— a file type the vault doesn't yet accept (e.g. Mathematica.nb). Decide with the user: add the extension toVaultService.ARTIFACT_EXTENSIONS(and re-run for those notes), convert it, or leave it out with a note.attachment … unresolved en-media hash …— an inline reference whose file was skipped above; it disappears from the body. Usually resolved by fixing the attachment type, then re-importing that note.
Evolving this skill (formats change)¶
Evernote (and other apps) keep changing their exports. When the importer reports an
unknown ENML element or an unhandled MIME type, work out the right mapping,
apply it to the affected notes with the normal vault tools, and propose an update:
either extend this SKILL.md's mapping table (so the next person knows) or, for a
recurring structural change, flag that importers/evernote.py / enml.py needs a
code change. Keep this file and the importer in sync — the importer's module docstring
points back here.
Tips¶
- Never try to re-do the bulk import yourself by calling tools per note — that's what the deterministic importer is for. Your value is judgment on the margins.
- The importer is idempotent only at the collection level (it creates a fresh collection); re-running makes a second collection. Prefer fixing in place.