A local-first backup control room for Apple Notes on macOS that surfaces read-only snapshot operations through MCP. Under the hood it wraps notes-exporter with path-aware exports, launchd scheduling, and health checks. The MCP interface exposes the same diagnostics and status commands you'd run through notesctl, so Claude can help you review backup freshness, troubleshoot drift, and surface recovery steps without turning into a hosted service. Reach for this when you want Apple Notes backups on a repeatable schedule and need an AI to read the same local ledger, logs, and proof surfaces you see in the Web console. It stays strictly read-only by design and keeps all state on your Mac.
Apple Notes Snapshot is the backup control room for Apple Notes on
macOS. Keep the upstream notes-exporter engine, then add path-aware exports,
launchd scheduling, visible health checks, and a calmer recovery path when
the local backup loop drifts.
🤝 Sister repo: looking to recover deleted notes after the fact, not back them up beforehand? See notes-recover — the forensic-grade reader for Apple Notes'
NoteStore.sqlite(recover deleted notes, browse the raw store, ship audit-ready bundles).notebackupcovers the before;notes-recovercovers the after. Together they cover the full Apple Notes lifecycle on macOS.
After the control room already makes sense, the repo also ships extra builder and diagnostics surfaces so humans and coding agents can inspect the same backup state without turning the workflow into a hosted service.
Start the 3-step quickstart | First-run troubleshooting | Open the proof page | Compare with upstream | Browse release history | Get support or routing help

launchd to schedule repeatable snapshots instead of babysitting manual exports.Category: Apple Notes backup control room. AI/agent hook: AI-assisted diagnostics plus optional coding-agent access once the operator workflow is already clear. Result: a calmer, more reviewable backup workflow on your own Mac.
Best fit: people who already rely on Apple Notes and want a calmer, reviewable backup routine on their own Mac.
Not the goal: cloud sync, team collaboration, or two-way write-back into Apple Notes.
If you only want the shortest truthful filter before reading deeper, use this table first:
| What you need to know | Current answer |
|---|---|
| Product thesis | an Apple Notes backup control room for macOS |
| First success | ./notesctl run --no-status -> ./notesctl install --minutes 30 --load -> ./notesctl verify |
| First proof surface | the proof page after one healthy local loop exists |
| Second ring only | AI Diagnose, Local Web API, and MCP come after the operator path already makes sense |
| What it must never be reduced to | a hosted Notes service, cloud sync product, or generic AI dashboard |
launchd turns a one-off export into a repeatable local rhythm.status, verify, doctor, logs, and proof pages all stay on the same local facts.If you want the shortest public evidence trail after that first pass, open the proof page. It collects the repo-owned gates, the GitHub-controlled release and Pages evidence, and the current access boundary in one place.
Treat the first healthy loop like a three-stop checklist, not like a dashboard to decode all at once.
./notesctl run --no-status once so macOS can surface permissions and
the local ledger can record a first successful baseline../notesctl install --minutes 30 --load after that first run so the
workflow becomes a repeatable launchd loop instead of a manual chore../notesctl verify first, then ./notesctl doctor only if warnings or
empty state remain.After that verified loop exists, the Web console, proof page, AI Diagnose, Local Web API, and MCP surfaces become much easier to read because they are all describing a baseline you already proved.
The public product front door is still the local backup control room. The lanes
below are for builders after the first healthy loop exists; they do not replace
Run -> Install -> Verify.
launchd loop, and the health check surface on your own Mac./notesctl mcp and the root server.jsonexamples/public-skills/notes-snapshot-control-room/
teaches hosts how to attach to that same local control room.mcpb packaging
plugins/apple-notes-snapshot-control-room/
and packaging/mcpb/ are host-shaped packaging
surfaces around the same local workflowUse the integration links below only after the operator loop already makes sense.
Secondary integration reads after the first healthy loop: AI Diagnose | Local Web API | MCP Provider | Distribution and listing boundaries | For Codex / Claude Code integrations
Start here if you want the shortest honest path from manual export to a repeatable local snapshot loop. The first-successful-run promise is 3 steps and about 3 minutes, not a zero-click install.
config/notes_snapshot.env and keep or change the default export path.# review config/notes_snapshot.env first
./notesctl run --no-status
./notesctl install --minutes 30 --load
./notesctl verify
./notesctl doctor
Fail fast:
./notesctl verify says FAIL: no last_success record; run ./notesctl run,
you have not completed the first manual export yet../notesctl permissions and the
public troubleshooting guide.Keep the operator lane and the maintainer lane separate.
./notesctl run --no-status./notesctl install --minutes 30 --load./notesctl verify./notesctl doctor./notesctl status --full./notesctl ai-diagnose, ./notesctl web, and ./notesctl mcp./notesctl clean-cache --dry-run./notesctl clean-cache./notesctl rebuild-dev-env./notesctl update-vendor./notesctl setup / ./notesctl self-healIf your goal is a healthy backup loop, stay in the operator lane first. The maintainer lane is for repo upkeep and contributor verification after the local workflow already makes sense.
The upstream notes-exporter
project is great when you want to export Apple Notes right now. This repository
exists for the moment when "run it once" turns into "keep it healthy every
day."
Apple Notes Snapshot wraps the upstream exporter with:
notesctllaunchdThink of upstream as the engine and this repository as the control room around it. The wrapper tells you where snapshots will land, and makes the schedule and health surface easier to inspect when something breaks.

| Need | Upstream notes-exporter | Apple Notes Snapshot |
|---|---|---|
| Export notes once | Yes | Yes |
| Schedule recurring exports | Manual setup | Built-in launchd flow |
| Check freshness and last success | Limited | status, verify, and Web health UI |
| See structured run metadata | Limited | State files, metrics, and summaries |
| Rotate logs and inspect failures | Manual | Wrapper-owned log handling |
| Use a local Web console | No | Optional, token-aware local control plane |
Use upstream directly if you only need a one-time export. Use this repository when you want a repeatable local backup loop with visible scheduler state, clear snapshot paths, and easier recovery when something fails.
The local Web console is optional, and it makes the most sense after you have
already completed the first run -> install -> verify pass. Use it to watch
the loop you just proved, not as a replacement for that first proof.
It surfaces:
These are additive surfaces around the same local control room. They do not
replace notesctl or the deterministic runtime checks.
./notesctl ai-diagnose when you want an advisory explanation of the
current local state. It reads status, doctor, log-health, and recent-run
summaries, routes model calls through a local Switchyard runtime when AI is
enabled, and still works as a deterministic fallback when no AI provider is
configured. Read the public
AI Diagnose guide../notesctl mcp when you want a stdio-first, read-only-first MCP surface
for agents. It exposes local backup diagnostics and resources without turning
the Web console into a fake MCP API. Read the public
MCP guide../notesctl web when you want the token-gated local browser control room
plus a small JSON API backed by the same repo-owned command surface. It is a
local operator API, not a public OpenAPI or hosted integration surface. Read
the public
Local Web API guide.Use this mental model if you care about Codex, Claude Code, MCP, or other host-local integration ecosystems.
Natural fit:
Codex- and Claude Code-style local workflows when the host can launch stdio MCP servers
MCP-aware coding agents that need the same local backup facts as a human operator
AI Diagnose = operator next-step assistant
MCP Provider = read-only agent substrate
Local Web API = token-gated browser/API surface
status,
doctor, recent-runs, and access.notesctl + state.json + aggregate summaries + token-gated Web API = current substrate
Integration-facing docs have their own shelves now, so this README does not need to carry every host-specific setup detail:

./notesctl run --no-status
./notesctl install --minutes 30 --load
This is the primary getting-started path. Everything else in the docs site is a supporting surface around that flow.
./notesctl status --full
./notesctl verify
./notesctl doctor
export NOTES_SNAPSHOT_WEB_TOKEN="<long-random-token>"
./notesctl web
./notesctl update-vendor --ref <tag|branch|sha>
launchd over cloud schedulersThis repository ships with repo-owned verification commands instead of asking you to trust screenshots alone. These are maintainer / contributor gates, not required for the first successful snapshot.
If you want the shorter public-facing evidence page first, open the proof page. It keeps the repo-side gates, GitHub-controlled delivery facts, and the current access boundary in one place. The ladder below remains the maintainer-grade verification contract.
Default local maintainer lane:
./notesctl rebuild-dev-env
./.runtime-cache/dev/venv/bin/python -m pre_commit run --all-files
PYTHON_BIN=./.runtime-cache/dev/venv/bin/python scripts/checks/ci_gate.sh
Maintainer verification expects a local Python 3.11+ toolchain. ./notesctl rebuild-dev-env
recreates .runtime-cache/dev/venv from scratch so the documented verification
commands stay aligned with the current checkout path and interpreter.
GitHub Actions for this repo run on GitHub-hosted runners; the local ladder
below is maintainer verification, not a self-hosted runner requirement.
Five-layer CI contract:
| Layer | Canonical home | What belongs here |
|---|---|---|
pre-commit | local hook | gitleaks, docs-link-root hygiene, legacy-path scan, and public-surface-sensitive scan |
pre-push | local hook | scripts/checks/ci_gate.sh for repo-local deterministic checks: docs/root hygiene, vendor tree hygiene, unit tests, and wrapper smoke |
hosted | GitHub Actions | Canonical Quick Gate, Secret Scan, GitHub Alert Gate, Dependency Review, Actionlint, Zizmor, Trivy, CodeQL, and Pages |
nightly | GitHub Actions schedule | Nightly Deterministic Audit reruns the repo-owned ladder on GitHub-hosted runners without making pre-push heavier |
manual | real machine / owner session | `notesctl run |
GitHub-only governance gates:
Dependency Review runs on pull requests because it needs GitHub's base/head
dependency diff.CodeQL, Secret Scan, GitHub Alert Gate, Actionlint, Zizmor, and
Trivy stay hosted-first; local reruns are optional maintainer repro steps,
not part of the default hook path.Think of the runtime layout as two rooms with one job each:
.runtime-cache/notesctlCurrent repo-local contract:
.runtime-cache/dev/venv -> repo-owned maintainer virtual environment.runtime-cache/cache/apple-notes-snapshot -> repo-local runtime cache/state support.runtime-cache/temp -> scratch.runtime-cache/logs -> repo-local logs.runtime-cache/pytest -> pytest cache.runtime-cache/coverage -> coverage data.runtime-cache/pycache -> Python bytecode cache.runtime-cache/browser-proof -> generated proof screenshots that can be rebuilt from the current docs surface.runtime-cache/phase1 -> historical hard-cut rollback artifacts, not current runtime truth.runtime-cache/phase1-history-rebuild -> historical rebuild rollback artifacts, not current runtime truth.runtime-cache/mcp-registry-lane/out -> release-ready MCP registry artifacts that can be rebuilt on demandCurrent external repo-owned contract:
Older machine-level Application Support and cache roots are migration inputs only. New repo-owned runtime/cache writes should stay inside the current repo-managed machine cache root.
This is a maintainer-only cleanup lane. It is useful when you are rebuilding verification tooling or reclaiming repo-owned runtime residue, not when you are trying to complete the first successful snapshot.
./notesctl clean-cache --dry-run
.runtime-cache/./notesctl clean-cache
./notesctl runtime-audit
./notesctl clean-runtime --dry-run
./notesctl clean-runtime
./notesctl browser-bootstrap
apple-notes-snapshot source profile out of the default Chrome root into the repo-owned isolated root./notesctl browser-open
./notesctl rebuild-dev-env
The repo may still clean legacy .pytest_cache, .coverage, or scattered
__pycache__ directories if they are already present, but those are migration
backstops. The current contract routes repo-owned disposable artifacts into
.runtime-cache/*, including historical rollback folders and proof captures
that are safe to regenerate when you no longer need them.
Automatic janitor hooks run on repo-owned entrypoints that create or reuse
machine-level residue, including run, web, install, ensure,
rebuild-dev-env, and runtime-audit. The default policy is intentionally
strict:
2 GB72 hours24 hoursbrowser/chrome-user-data/ is permanent state and excluded from TTL/cap cleanupThis repository does not have a repo-owned Docker cleanup lane today. The automatic janitor does not touch Docker, system temp roots, or shared tool caches from Cursor, Codex, Claude, Serena, Homebrew, pip, nodeenv, uv, or other machine-wide tooling.
Browser automation in this repository now uses an isolated Chrome root + single repo-owned instance + CDP attach contract.
NOTES_SNAPSHOT_BROWSER_PROVIDER=chromeNOTES_SNAPSHOT_BROWSER_ROOT=<repo-owned-browser-root>NOTES_SNAPSHOT_CHROME_USER_DATA_DIR=<repo-owned-browser-root>/chrome-user-dataNOTES_SNAPSHOT_CHROME_PROFILE_NAME=apple-notes-snapshotNOTES_SNAPSHOT_CHROME_PROFILE_DIR=Profile 1NOTES_SNAPSHOT_CHROME_CDP_HOST=127.0.0.1NOTES_SNAPSHOT_CHROME_CDP_PORT=9337The old default Chrome user-data root is now only a one-time read source for
./notesctl browser-bootstrap. It is no longer the long-term runtime root for
this repo.
Treat browser-bootstrap as a one-time migration, not as a routine sync step.
If you later add or refresh logins inside the isolated root, do not rerun
browser-bootstrap unless you intentionally want to replace the isolated root
from the default Chrome root again.
Use these commands in order:
./notesctl browser-bootstrap
./notesctl browser-open
./notesctl browser-contract --json
If 127.0.0.1:9337 is already occupied on your machine, the repo should fail
fast instead of silently attaching to the wrong thing. In that case, use a
deliberate env override such as NOTES_SNAPSHOT_CHROME_CDP_PORT=9347 before
starting or attaching to the repo-owned instance.
This repo does not silently fall back to bundled Chromium, and it does not keep launching new browser instances against the same user-data dir. Human manual use and automation are expected to attach to the same repo-owned Chrome instance.
The repository keeps five verification layers, and they are not interchangeable:
| Layer | Default trigger | Canonical home | Contract |
|---|---|---|---|
pre-commit | every local commit attempt | local hook | quick hygiene only |
pre-push | every local push attempt | local hook | deterministic repo-local quick gate only |
hosted | pull request / push / workflow dispatch | GitHub-hosted runners | GitHub-state-aware security and policy gates |
nightly | scheduled GitHub run | Nightly Deterministic Audit reruns the repo-owned ladder on GitHub-hosted runners | |
manual | deliberate human/operator action | real machine / owner session | live browser, desktop, provider, and external control-plane proof |
This open-source repository does not rely on a local self-hosted runner lane. Local verification exists so maintainers can reproduce the repo-owned gates before or after a pull request, not because CI is expected to run on the developer's Mac.
High-value local checks:
./notesctl status --full
./notesctl verify
./notesctl doctor
The repo-owned quick gate now covers docs/root hygiene, vendor tree hygiene,
unit tests, and wrapper-level JSON/help smoke checks. GitHub alert state moved
fully into the hosted lane so the default local pre-push path stays lighter and
more deterministic. The 90%+ coverage bar still applies to repo-owned Python
surfaces under scripts/ops; the shell wrapper surface is guarded by smoke
checks rather than pretending it shares that coverage metric.
Think of the verification contract like a small testing pyramid instead of one giant "just run everything" blob.
tests/unit/test_ai_diagnose_unit.py,
tests/unit/test_mcp_server_unit.py,
tests/unit/test_web_server_unit.pynotesctl help, JSON surfaces, and
wrapper entrypointsbash scripts/checks/run_wrapper_smoke.sh./.runtime-cache/dev/venv/bin/pytest tests/e2e --no-covrun -> verify -> status -> doctor -> webRead the pyramid as:
This project is intentionally user-controlled on your own machine:
SECURITY.mdRead SECURITY.md before reporting sensitive issues.
No. It is a wrapper that makes the upstream exporter safer and easier to operate as an ongoing local workflow.
No. This repository is for snapshotting and exporting, not two-way sync.
No. notesctl is the supported human entrypoint. The Web console is optional.
Run ./notesctl with no arguments. The interactive menu lives on the canonical
command surface now.
No. Pages is only for public documentation and search visibility. The actual workflow stays local on your Mac.
Reinstall the scheduler so the path-aware launchd surface points at the current checkout. If you also use the repo-owned verification environment or the optional Web console, rebuild that environment too:
./notesctl install --minutes 30 --load
./notesctl rebuild-dev-env
Use the GitHub Discussions surface that matches your intent:
If you need a support-routing summary first, read SUPPORT.md or the public support page.
Read CONTRIBUTING.md before changing wrapper logic, docs, or public-facing copy. The project favors small, reviewable changes with explicit verification notes.
This repository is released under the MIT License.
gongrzhe/office-powerpoint-mcp-server
gongrzhe/office-word-mcp-server
io.github.mindstone/mcp-server-office
greirson/mcp-todoist
henilcalagiya/mcp-apple-notes
ankimcp/anki-mcp-server-addon