4 Commits
Author SHA1 Message Date
ingjieyeandClaude Opus 5 ad668fbcd3 Ship the agent skill in-repo and document installing it
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>
2026-09-09 16:48:48 +08:00
ingjieyeandClaude Opus 5 07bf3854b7 README: state plainly that the Notes connector uses AppleScript
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>
2026-09-09 16:46:23 +08:00
ingjieyeandClaude Opus 5 cb81fb4605 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>
2026-09-09 16:44:04 +08:00
ingjieyeandClaude Opus 5 a8615b3c45 Read Apple Notes directly from its SQLite store
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>
2026-09-09 16:38:00 +08:00