apple-notes — manage Apple Notes locally via AppleScript
Drives the user's real Apple Notes through Notes.app on macOS. Apple never
shipped a cloud API for Notes, so there is nothing to OAuth into and no token to
inject — this skill runs on the user's own Mac and talks to Notes.app with
AppleScript (osascript). It works wherever an agent runs on macOS: the
AceDataCloud desktop app (which executes local tools / local MCP on the
user's Mac), Claude Code on macOS, or a CodingBridge node on the user's Mac.
surfaces: [mac] in the frontmatter marks this skill as macOS-desktop
only — it cannot run on the web / iOS / Android / Windows surfaces (there is
no local Notes.app there). The skill/connector directory UI reads this to
badge the card ("macOS desktop") and steer users to install it from the macOS
desktop app. Absent surfaces, a skill is assumed available everywhere.
The skill ships scripts/notes.py — self-contained, Python
standard library only (it shells out to osascript; no pip install, no
brew). Dynamic values are passed as environment variables, never interpolated
into the AppleScript source, so note content can't break the script or inject
commands.
Requirements
- macOS with the Notes app.
python3 (system Python is fine).
- Automation permission. The first call triggers a macOS prompt: "Terminal
wants to control Notes." Approve it, or enable it under System Settings ›
Privacy & Security › Automation › (your terminal / agent) › Notes. On an
authorization error the CLI tells the user exactly this — do not loop-retry.
- Referencing Notes launches the app in the background (no window is forced open).
NOTES="${SKILL_DIR:-.}/scripts/notes.py" # run from this skill's directory
python3 "$NOTES" folders # smoke-test the connection
Commands
| Command |
Read/Write |
Purpose |
folders |
read |
List folders with note counts |
list [--folder F] [--limit N] |
read |
List notes (newest first): id, title, folder, dates |
search QUERY [--folder F] [--limit N] |
read |
Case-insensitive substring match on title + body |
view NOTE_ID [--format json|text|html] |
read |
Show one note (metadata + body) |
export NOTE_ID [--format md|html|text] [-o FILE] |
read |
Export one note to Markdown/HTML/text |
create --title T [--body B | --body-file F] [--folder F] |
write |
Create a note |
append NOTE_ID [--body B | --body-file F] |
write |
Append text to an existing note |
move NOTE_ID --folder F |
write |
Move a note to another folder |
new-folder NAME |
write |
Create a folder |
delete NOTE_ID |
write |
Move a note to Recently Deleted (recoverable) |
Most commands print JSON (and errors are always JSON: {"error": ...}). The
exceptions print raw note content to stdout: view --format text|html and
export without -o. NOTE_ID is the opaque x-coredata://… id returned by
list / search — always fetch a fresh id first; do not guess one.
Read examples
python3 "$NOTES" folders
python3 "$NOTES" list --limit 20
python3 "$NOTES" list --folder "Work" --limit 50
python3 "$NOTES" search "invoice" --limit 20
python3 "$NOTES" view "x-coredata://…/p42"
python3 "$NOTES" export "x-coredata://…/p42" --format md -o note.md
Writes are GATED (dry-run unless trailing --confirm)
create, append, move, new-folder, and delete change the user's real
notes. Without a trailing --confirm they dry-run and print what they would
do. --confirm is honored only as the very last argument. Always show the
dry-run, get an explicit "yes", then re-run with --confirm appended.
# Create — dry-run, then confirm
python3 "$NOTES" create --title "Groceries" --body "milk\neggs" # preview
python3 "$NOTES" create --title "Groceries" --body "milk\neggs" --confirm # writes
# Create in a folder, body from a file
python3 "$NOTES" create --title "Spec" --body-file draft.md --folder "Work" --confirm
# Append to an existing note
python3 "$NOTES" append "x-coredata://…/p42" --body "one more line" --confirm
# Move / organize
python3 "$NOTES" new-folder "Archive" --confirm
python3 "$NOTES" move "x-coredata://…/p42" --folder "Archive" --confirm
# Delete → goes to Recently Deleted (recoverable ~30 days)
python3 "$NOTES" delete "x-coredata://…/p42" --confirm
Content is treated as plain text (each line becomes a paragraph; the
--title is the note's first line, which Notes shows as the title). HTML in the
input is escaped, so it can't inject markup. Use \n for line breaks in
--body, or pass a file with --body-file.
Gotchas
- This is the user's real Notes account. Confirm before any write.
list scans the whole library — for large accounts it can take a few
seconds. Narrow with --folder, and cap with --limit (default 50). search
filters inside Notes and is faster.
- Attachments / images: notes containing images or attachments can be read
and exported as text, but
append may not preserve embedded attachments —
avoid editing attachment notes here; open them in Notes instead.
- Export → Markdown converts common Notes formatting (headings, bold/italic,
lists, links, line breaks). Rich objects (tables, drawings, scanned docs)
degrade to plain text.
move stays within one account (e.g. iCloud). Cross-account moves fail.
- Never assume a
NOTE_ID — ids are store-specific; always get them from
list / search first.
- On a persistent authorization error, the Automation permission is missing —
tell the user to grant it (see Requirements) and stop; do not retry in a loop.
1---
2name: apple-notes
3description: Manage Apple Notes on macOS — list, search, read, create, append, move, delete, and export notes, and manage folders. Use when the user mentions Apple Notes / 备忘录, "add a note", "my notes", searching their notes, organizing folders, or exporting a note to Markdown/HTML. macOS-only; runs locally against Notes.app — no cloud API, no token.
4when_to_use: |
5 Trigger for anything on the user's local Apple Notes (Notes.app on macOS):
6 list / search / read / export a note, create a new one, append to an existing
7 one, move it between folders, or manage folders. There is no cloud API for
8 Apple Notes, so this runs on the user's own Mac (Claude Code on macOS, or
9 driven remotely through a CodingBridge node). Writes act on the user's REAL
10 notes, so create / append / move / delete are GATED behind an explicit
11 confirmation.
12allowed_tools: [Bash]
13surfaces: [mac]
14license: Apache-2.0
15compatibility: macOS-only. No API token, no connector — Apple Notes has no cloud API. Drives the local Notes.app via AppleScript (osascript); zero external dependencies (Python 3 standard library only). Requires granting Automation access to Notes.app on first run. Does not call api.acedata.cloud.
16metadata:
17 author: acedatacloud
18 version: "1.0"
19---
20
21# apple-notes — manage Apple Notes locally via AppleScript
22
23Drives the user's **real** Apple Notes through `Notes.app` on macOS. Apple never
24shipped a cloud API for Notes, so there is nothing to OAuth into and no token to
25inject — this skill runs **on the user's own Mac** and talks to Notes.app with
26AppleScript (`osascript`). It works wherever an agent runs on macOS: the
27**AceDataCloud desktop app** (which executes local tools / local MCP on the
28user's Mac), Claude Code on macOS, or a CodingBridge node on the user's Mac.
29
30> **`surfaces: [mac]`** in the frontmatter marks this skill as **macOS-desktop
31> only** — it cannot run on the web / iOS / Android / Windows surfaces (there is
32> no local `Notes.app` there). The skill/connector directory UI reads this to
33> badge the card ("macOS desktop") and steer users to install it from the macOS
34> desktop app. Absent `surfaces`, a skill is assumed available everywhere.
35
36The skill ships [`scripts/notes.py`](scripts/notes.py) — self-contained, Python
37**standard library only** (it shells out to `osascript`; no `pip install`, no
38`brew`). Dynamic values are passed as environment variables, never interpolated
39into the AppleScript source, so note content can't break the script or inject
40commands.
41
42## Requirements
43
44- **macOS** with the Notes app. `python3` (system Python is fine).
45- **Automation permission.** The first call triggers a macOS prompt: *"Terminal
46 wants to control Notes."* Approve it, or enable it under **System Settings ›
47 Privacy & Security › Automation ›** (your terminal / agent) **› Notes**. On an
48 authorization error the CLI tells the user exactly this — do **not** loop-retry.
49- Referencing Notes launches the app in the background (no window is forced open).
50
51```sh
52NOTES="${SKILL_DIR:-.}/scripts/notes.py" # run from this skill's directory
53python3 "$NOTES" folders # smoke-test the connection
54```
55
56## Commands
57
58| Command | Read/Write | Purpose |
59| --- | --- | --- |
60| `folders` | read | List folders with note counts |
61| `list [--folder F] [--limit N]` | read | List notes (newest first): id, title, folder, dates |
62| `search QUERY [--folder F] [--limit N]` | read | Case-insensitive substring match on title + body |
63| `view NOTE_ID [--format json\|text\|html]` | read | Show one note (metadata + body) |
64| `export NOTE_ID [--format md\|html\|text] [-o FILE]` | read | Export one note to Markdown/HTML/text |
65| `create --title T [--body B \| --body-file F] [--folder F]` | **write** | Create a note |
66| `append NOTE_ID [--body B \| --body-file F]` | **write** | Append text to an existing note |
67| `move NOTE_ID --folder F` | **write** | Move a note to another folder |
68| `new-folder NAME` | **write** | Create a folder |
69| `delete NOTE_ID` | **write** | Move a note to *Recently Deleted* (recoverable) |
70
71Most commands print JSON (and errors are always JSON: `{"error": ...}`). The
72exceptions print raw note content to stdout: `view --format text|html` and
73`export` without `-o`. `NOTE_ID` is the opaque `x-coredata://…` id returned by
74`list` / `search` — always fetch a fresh id first; do not guess one.
75
76## Read examples
77
78```sh
79python3 "$NOTES" folders
80python3 "$NOTES" list --limit 20
81python3 "$NOTES" list --folder "Work" --limit 50
82python3 "$NOTES" search "invoice" --limit 20
83python3 "$NOTES" view "x-coredata://…/p42"
84python3 "$NOTES" export "x-coredata://…/p42" --format md -o note.md
85```
86
87## Writes are GATED (dry-run unless trailing `--confirm`)
88
89`create`, `append`, `move`, `new-folder`, and `delete` change the user's real
90notes. Without a trailing `--confirm` they **dry-run** and print what they would
91do. `--confirm` is honored **only as the very last argument**. Always show the
92dry-run, get an explicit "yes", then re-run with `--confirm` appended.
93
94```sh
95# Create — dry-run, then confirm
96python3 "$NOTES" create --title "Groceries" --body "milk\neggs" # preview
97python3 "$NOTES" create --title "Groceries" --body "milk\neggs" --confirm # writes
98
99# Create in a folder, body from a file
100python3 "$NOTES" create --title "Spec" --body-file draft.md --folder "Work" --confirm
101
102# Append to an existing note
103python3 "$NOTES" append "x-coredata://…/p42" --body "one more line" --confirm
104
105# Move / organize
106python3 "$NOTES" new-folder "Archive" --confirm
107python3 "$NOTES" move "x-coredata://…/p42" --folder "Archive" --confirm
108
109# Delete → goes to Recently Deleted (recoverable ~30 days)
110python3 "$NOTES" delete "x-coredata://…/p42" --confirm
111```
112
113Content is treated as **plain text** (each line becomes a paragraph; the
114`--title` is the note's first line, which Notes shows as the title). HTML in the
115input is escaped, so it can't inject markup. Use `\n` for line breaks in
116`--body`, or pass a file with `--body-file`.
117
118## Gotchas
119
120- **This is the user's real Notes account.** Confirm before any write.
121- **`list` scans the whole library** — for large accounts it can take a few
122 seconds. Narrow with `--folder`, and cap with `--limit` (default 50). `search`
123 filters inside Notes and is faster.
124- **Attachments / images:** notes containing images or attachments can be read
125 and exported as text, but `append` may not preserve embedded attachments —
126 avoid editing attachment notes here; open them in Notes instead.
127- **Export → Markdown** converts common Notes formatting (headings, bold/italic,
128 lists, links, line breaks). Rich objects (tables, drawings, scanned docs)
129 degrade to plain text.
130- **`move` stays within one account** (e.g. iCloud). Cross-account moves fail.
131- **Never assume a `NOTE_ID`** — ids are store-specific; always get them from
132 `list` / `search` first.
133- On a persistent authorization error, the Automation permission is missing —
134 tell the user to grant it (see Requirements) and stop; do not retry in a loop.