README: frame this as read-first and detail the AppleScript trade-offs

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>
This commit is contained in:
ingjieyeandClaude Opus 5 committed 2026-09-09 16:44:04 +08:00
1 parent a8615b3c45
commit cb81fb4605
1 file changed
+53 -7
+53 -7
View File
@@ -3,6 +3,11 @@
Search, read, and export Apple Notes from the terminal by reading the local
Notes database directly — no AppleScript, no UI automation, no cloud API.
Built for the common case: **you mostly want to look things up, not write
them.** Recalling what you noted about a project, finding the meeting where a
decision was made, pulling every note that mentions a library — that is
read-heavy work, and it is exactly where the existing tooling is weakest.
```console
$ apple-notes search "kubernetes" -n 3
4821 2026-09-08 14:02 Work/Infra/Cluster upgrade notes (7x)
@@ -17,8 +22,19 @@ $ apple-notes recent --since 2d
## Why not AppleScript
The usual approach is `osascript` against Notes.app. It works, but the cost
lands squarely on search. Measured on a real 818-note store:
Almost every Apple Notes integration drives Notes.app through AppleScript
(`osascript`). The [`memo`](https://github.com/antoniorodr/memo) CLI does — its
`get_memo.py` and `id_search_memo.py` shell out to `osascript` — and so does
the [`apple-notes` skill in
OpenClaw](https://github.com/openclaw/openclaw/blob/main/skills/apple-notes/SKILL.md)
and Hermes, which wrap `memo`. Claude's own [Notes
connector](https://support.claude.com/en/articles/11176164-use-connectors-to-extend-claude-s-capabilities)
is likewise a desktop-only, macOS-only extension; its implementation isn't
public, but Apple Events is the only supported way in.
AppleScript is the right choice for *writing* — it is Apple's supported
interface and it keeps CloudKit sync intact. But for reading it carries costs
that are easy to miss. Measured on a real 818-note store:
| Operation | AppleScript | apple-notes-cli |
| --- | --- | --- |
@@ -26,11 +42,41 @@ lands squarely on search. Measured on a real 818-note store:
| Fetch one note body | 0.325 s | — |
| **Full-text search every note** | **~266 s** (extrapolated) | **0.11 s** |
`first note whose id is "…"` is a linear scan inside Notes.app with no index
behind it, so reading every body costs a third of a second each. That is why
AppleScript-based tools either search titles only or cache aggressively and
serve you stale results. Reading the SQLite store sidesteps the whole problem:
one query, then in-memory decoding of every body.
**Full-text search is impractical, so tools quietly stop doing it.**
`first note whose id is "…"` is an unindexed linear scan inside Notes.app, so
each body costs about a third of a second. Reading all 818 would take over four
minutes. Faced with that, `memo` searches **titles only** — its
`search_memo.py` builds a `{title: id}` map and fuzzy-matches against the keys,
never the bodies. So `memo notes -s "kubernetes"` finds notes *called*
"kubernetes", not the notes that discuss it. The OpenClaw skill exposes this as
"fuzzy search" with no indication that note contents are not searched.
**Caching trades freshness for speed, usually silently.** `memo` caches its
note list to `~/.cache/memo/notes_cache.json` with a 300-second TTL, so a note
you just wrote can be invisible for five minutes. Neither the skill
documentation nor the CLI surfaces this, so an agent asking "what did I note
today?" can be handed a confidently stale answer.
**Other friction that comes with the AppleScript path:**
- **It needs Notes.app.** The app gets launched or scripted in the background,
and if it hangs or shows a modal, the integration hangs with it.
- **It needs Automation permission** (System Settings → Privacy & Security →
Automation), granted interactively per client app. That makes unattended use
— cron, CI, an SSH session — awkward or impossible.
- **Bodies arrive as HTML**, so every tool bolts on an HTML-to-Markdown
converter and inherits its fidelity bugs.
- **macOS only**, and it cannot run from the web or iOS at all.
- **Per-note round trips.** Reading N notes means N `osascript` invocations,
each paying process startup plus Apple Events IPC.
Reading the SQLite store sidesteps all of it: one query, then in-memory
decoding. Full-text search over everything takes ~0.11 s, which is why this
tool needs no cache and always reflects the live database.
The trade-off is that this only reads; AppleScript remains the right way to
create and edit notes. The two compose well — see
[Reading is all it does](#reading-is-all-it-does).
## Install