The skill only existed on my machine, so nobody else could get it. It now
lives at skills/apple-notes/SKILL.md and the README covers installing it
alongside the CLI.
Adds an agent-driven install path: pointing an agent at the README is enough,
since it now carries a collapsed block of literal setup steps -- platform
check, CLI install with a pipx fallback, skill fetch, and a verification run --
plus what the two likely failures (no database, no permission) mean.
Drops the local-checkout path from the skill's install hint, which was only
valid on my machine.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Confirmed by direct verification against Claude Desktop, so drop the hedge
about the implementation not being public and list the connector alongside
memo and the OpenClaw/Hermes skills.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The common case is looking things up, not writing, so say so up front and
explain why the existing tooling struggles there.
Names what the ecosystem actually does: memo shells out to osascript, the
OpenClaw and Hermes apple-notes skills wrap memo, and Claude's Notes connector
is a desktop-and-macOS-only extension. The connector's implementation is not
public, so this claims only that Apple Events is the sole supported route in
rather than asserting its internals.
Spells out the read-path costs, the first two verified from memo's source:
- Full-text search is impractical at ~0.325s per body, so memo matches titles
only -- search_memo.py fuzzy-matches a {title: id} map and never touches
bodies, while the OpenClaw skill still presents it as "fuzzy search".
- memo's 300s cache TTL can hide a note you just wrote, and neither the CLI
nor the skill docs mention it.
- Plus needing Notes.app running, interactive Automation permission (so no
cron/CI/SSH), HTML bodies requiring conversion, macOS-only, and one
osascript round trip per note.
Also drops the duplicate write-path paragraph, which restated the existing
"Reading is all it does" section.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Splits the original single-file exporter into a package and adds a query
layer, so notes can be searched and read live instead of only exported.
Reading the Core Data store rather than driving Notes.app through AppleScript
is what makes full-text search practical. Measured on an 818-note store,
osascript needs ~0.325s per note body (`first note whose id is` is an
unindexed linear scan), so searching everything costs minutes; decoding every
gzipped protobuf body here takes 0.11s total. No cache, so results are always
current.
- store.py read-only SQLite access, folder tree, attachments
- parser.py gzip + protobuf decoding and Markdown rendering (moved verbatim)
- search.py search, recency listing, single-note reads
- export.py Markdown output and the incremental manifest
Three correctness details found while building this:
- Open with mode=ro, never immutable=1. The immutable flag makes SQLite skip
the -wal file and silently serve a stale snapshot; the live store routinely
carries megabytes of un-checkpointed WAL.
- Recently Deleted is ZFOLDERTYPE = 1, and notes in it keep
ZMARKEDFORDELETION = 0, so the existing filter still surfaced them. Matching
the folder type is also language-independent. Now excluded by default.
- Creation timestamps live in whichever of ZCREATIONDATE..3 is populated, so
every candidate column is read.
Export no longer writes a CLAUDE.md into the output directory unless asked
with --write-claude-md.
tests/test_parity.py keeps the original suite byte-for-byte as a regression
check on the split; test_search.py covers the new query layer against a
synthetic store. 48 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>