From notes-commander
Park a draft message/text in macOS Notes for the operator to review and edit, then read it back before acting (e.g. before sending to a real person). Notes is the source of truth (AppleScript CRUD, iCloud-synced, provenance-stamped with the Claude Code session UUID); Stickies is a best-effort view-only desktop mirror. Use whenever you draft something a human should confirm/edit before it is sent or committed — messages, replies, announcements, anything outbound. TRIGGERS - hold this draft, park the message, let me edit first, draft for my approval, save to notes for review, read back the draft.
How this skill is triggered — by the user, by Claude, or both
Slash command
/notes-commander:draft-holdThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
> **Self-Evolving skill** — if macOS Notes/Stickies behavior drifts from what's below, fix this SKILL.md and the shared engine `scripts/lib/notes-core.ts` (+ a case in `notes-core.test.ts`); see the Post-Execution Reflection at the bottom.
Self-Evolving skill — if macOS Notes/Stickies behavior drifts from what's below, fix this SKILL.md and the shared engine
scripts/lib/notes-core.ts(+ a case innotes-core.test.ts); see the Post-Execution Reflection at the bottom.
When you compose something a human should confirm or edit before it goes out (a message to a real person, an announcement, a commit body), don't keep it only in chat — park it in macOS Notes so the operator can edit it on any device, then read it back and act on the edited version.
DH="$CLAUDE_PLUGIN_ROOT/skills/draft-hold/draft-hold.sh" — a thin shim that execs the Bun/TypeScript engine scripts/draft-hold.ts, built on the plugin's shared notes-core engine (which also powers notes-inventory/notes-export/notes-organize).
The engine (formatter in scripts/lib/notes-core.ts, unit-tested in notes-core.test.ts) normalizes your input into Notes HTML, so you never hand-manage line breaks:
-, *, +, •, 1., 2), a. etc. each stay on their own line; a wrapped continuation line (indented, no marker) joins back to its item.``` fence. Every line inside is preserved exactly and rendered monospace with spaces held (via ), so columns and IDs line up in the Notes UI. (Note: get --body-only returns the sendable plain text and collapses inter-column runs to single spaces; if exact alignment must survive to the recipient, send an attachment/screenshot.)new asserts Notes returned a real note id (x-coredata://…); on recent macOS, osascript can exit 0 yet create nothing. If that happens you get a loud ✗ SILENT-FAILURE instead of a phantom "success".-600/-1712/"not running") retry with backoff; permission/syntax errors fail fast.new, the note is read back and checked for entity leaks and content presence. --no-verify skips (rarely needed).Park the draft (body on STDIN):
CLAUDE_SESSION_ID=<this-session-uuid> "$DH" new "<title>" --project "<repo-or-context>" <<'EOF'
Hi <name> — <your drafted message>...
EOF
Creates/replaces a note in the "Claude Drafts" folder with a provenance footer (session UUID + project + timestamp). Tell the operator: "Draft is in Notes → Claude Drafts → ; edit it there, then tell me to send."
(Optional) desktop mirror — "$DH" sticky "<title>" pops a view-only Stickies note (needs Accessibility permission). Notes stays authoritative.
Read it back before acting — ALWAYS re-read, since the operator may have edited it:
"$DH" get "<title>" # full note (heading + message + provenance footer)
"$DH" get "<title>" --body-only # JUST the sendable message (no heading, no footer)
Use --body-only to get exactly the text to send/paste — it strips the title heading and everything from the ------ provenance separator onward. Show the operator the exact current text, get explicit go-ahead, then send/commit.
"$DH" list enumerates held drafts.
body getter re-emits every " as the semicolon-less legacy entity " (verified 2026-06-29). We decode with textutil (a real HTML parser) instead of sed, so "/&/< etc. round-trip back to literal characters. Never hand-roll entity decoding here. The read-back verify now DETECTS a drift here automatically (✗ ENTITY-LEAK). A real HTML parser is not sufficient on its own (fixed 2026-07-20): because the stored form is semicolon-LESS, a note holding Write-Host "x"; $y comes back raw as Write-Host "x" $y, and textutil reads the closing " plus the author's own ; as one entity — silently eating the semicolon, so it round-tripped as Write-Host "x" $y. That corrupts most staged PowerShell/C/Java/JS with no error, which is fatal for a skill whose job is staging text a human will SEND. terminateLegacyEntities() in notes-core.ts now appends the ; to every bare entity before decoding, so a following literal ; stays literal. This is safe unconditionally because Notes escapes every & it stores (an author's literal & comes back as &amp;), so a terminated entity in Notes output can only ever be bare-entity + the author's semicolon.textutil -format html assumes Latin-1 when the HTML has no charset declaration, mojibaking every non-ASCII character (关于 → 关于; verified 2026-07-02 with a Chinese draft). htmlToText therefore prepends <meta charset="utf-8"> before piping to textutil — keep that prefix.new therefore prepends the title as a bold first line so get/list/replace can find it by title. Pass the message body only on STDIN. But Notes truncates a long first line to a capped name ending in an ellipsis … (verified 2026-07-20: a 66-char title stored as …(2026-07-20…), so an exact whose name is <title> lookup MISSES long-titled notes. The engine handles this: new reads its note back by id (not title) for the verify, and get/sticky/move-note resolve a title to a note truncation-tolerantly via noteNameMatchesTitle() (exact, else the stored name is the title truncated with a trailing …). Never rely on exact-name AppleScript matching for a possibly-long title.``` fenced blocks are wrapped in <tt> (Notes' "Monostyled" face) with spaces held as . The mono face is fixed by Notes — it is not the global NSFixedPitchFont, which governs TextEdit-style apps, not Notes.Pass the current Claude Code session JSONL UUID via CLAUDE_SESSION_ID or --session so the note traces back to its session. If you don't know it, the statusline-tools:session-info skill reports it, or read the newest *.jsonl under ~/.claude/projects/<project-slug>/. If genuinely unavailable, omit it (the footer drops the token — never write a placeholder).
get the note first; the operator may have changed it.open x-coredata://… fails; applenotes: links are UI-only) — reference drafts by folder + title.noteNameMatchesTitle() in notes-core.ts (used by draft-hold) AND a hand-copied AppleScript scan inside notes.ts OSA_MOVE, guarded only by a "keep the two rules in step" comment. Fix: added pure matchNoteIds(index, title) to notes-core.ts (exact ids first, else truncation-tolerant; returns every match so callers can flag ambiguity). move-note now indexes the source folder (OSA_NOTE_INDEX_BY_PATH), resolves the title in TS via matchNoteIds, and moves by id (OSA_MOVE_BY_ID); the name-matching AppleScript is deleted. draft-hold's own resolver + dedup route through matchNoteIds too. One rule, unit-tested (5 new tests, 30 total), impossible to drift. Invariant recorded in the plugin CLAUDE.md (#8).move-note (name truncation). Trigger: parking a 66-char draft ("CPC Scanners — Procurement Intelligence & Deliverables (2026-07-20)") returned a false ✗ CONTENT-MISMATCH, and move-note "<that title>" then failed with "note not found in source folder", even though the note existed and its body was correct. Root cause: macOS Notes stores a long first-line name TRUNCATED with a trailing …, so new's read-back (body of note <title> of folder) and move-note's whose name is <title> both looked up a name that no longer equalled the title. Fix: added the pure, unit-tested noteNameMatchesTitle() + NOTES_NAME_ELLIPSIS to notes-core.ts (exact, else truncated-prefix match); new now verifies by id (OSA_GET_BY_ID) and dedups older copies via a JS-side folder index; get/sticky resolve title→id truncation-tolerantly; move-note (notes.ts) falls back to a truncated-prefix scan that captures matches by id (a live notes of src reference held past the loop throws -1728). Evidence: a 125-char-title probe now creates (clean id, no CONTENT-MISMATCH), gets its body, and move-notes across folders — all green; 5 new unit tests (25 pass total). Verified live on macOS Notes.notes-commander plugin as one of its skills; formatting + process wrappers moved to the shared scripts/lib/notes-core.ts engine. Added, per a web-researched audit of recent macOS AppleScript failure modes: silent-no-op detection (isNoteId on create — osascript can exit 0 yet create nothing on macOS 26), bounded retry on transient AppleEvent errors (-600/-1712), and a default-on read-back verify (entityLeaks + contentPresent). All pure helpers unit-tested (16 tests in notes-core.test.ts).new made each input line its own Notes paragraph (all wrapped in <tt>), the reader saw mid-sentence line breaks that did not reflow. First fix (insufficient): a "one line per paragraph" caller contract — but that only works if every caller remembers it. Real fix: reimplemented the engine as Bun/TypeScript, enforcing the formatting in code so the failure is impossible: prose blocks REFLOW (consecutive lines join; blank line = paragraph), list markers stay per-item, and only ``` fenced blocks are preserved verbatim/monospace (spaces held as ). Verified by unit tests + a live Notes round-trip.After holding or sending a draft, check before closing:
"), the decode path drifted; the verify step should have caught it — fix the textutil step in notes-core.ts, never hand-roll sed.get/list find the note by title? — if not, the name==title assumption broke; fix the title-prepend in new.Only update if the issue is real and reproducible — not speculative.
npx claudepluginhub terrylica/cc-skills --plugin notes-commanderGuides collaborative design exploration before implementation: explores context, asks clarifying questions, proposes approaches, and writes a design doc for user approval.
Creates structured, bite-sized implementation plans from specs or requirements before writing code. Useful for breaking down multi-step tasks into testable steps with file structure and task boundaries.
Resolves in-progress git merge or rebase conflicts by analyzing history, understanding intent, and preserving both changes where possible. Runs automated checks after resolution.