Claude-code-plugins-plus-skills apple-notes-reference-architecture

install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/apple-notes-pack/skills/apple-notes-reference-architecture" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-apple-notes-reference-architectu && rm -rf "$T"
manifest: plugins/saas-packs/apple-notes-pack/skills/apple-notes-reference-architecture/SKILL.md
source content

Apple Notes Reference Architecture

Overview

Apple Notes automation systems are fundamentally different from cloud SaaS integrations. There is no REST API, no server-side SDK, and no webhook infrastructure. Everything runs locally on macOS through the Apple Events IPC bridge. This reference architecture defines the standard layered approach: a Node.js application layer that calls JXA scripts via

osascript
, a local SQLite cache for fast queries, a change detection poller for event-driven workflows, and optional Shortcuts integration for cross-app automation.

System Architecture

┌─────────────────────────────────────────────────────┐
│                    macOS Machine                      │
│                                                       │
│  ┌──────────┐   ┌───────────┐   ┌────────────────┐  │
│  │ Your App │──▶│ osascript  │──▶│   Notes.app    │  │
│  │ (Node.js)│   │  (JXA)    │   │  (local DB)    │  │
│  └────┬─────┘   └───────────┘   └───────┬────────┘  │
│       │                                   │           │
│  ┌────▼─────┐   ┌───────────┐   ┌───────▼────────┐  │
│  │ SQLite   │   │ Shortcuts │   │  iCloud Sync   │  │
│  │ Cache    │   │ Automations│   │ (bird/cloudd)  │  │
│  └──────────┘   └───────────┘   └────────────────┘  │
│       │                                   │           │
│  ┌────▼─────┐                    ┌────────▼───────┐  │
│  │ Poller / │                    │  Other Apple   │  │
│  │ FSEvents │                    │  Devices       │  │
│  └──────────┘                    └────────────────┘  │
└─────────────────────────────────────────────────────┘

Project Structure

apple-notes-automation/
├── src/
│   ├── notes-client.ts        # JXA wrapper class (osascript calls)
│   ├── cache.ts               # SQLite cache layer
│   ├── templates/             # Note templates (HTML fragments)
│   ├── export/                # Export to MD/JSON/SQLite/CSV
│   ├── events/                # Change detection via polling
│   └── server.ts              # Optional: local HTTP API for remote access
├── scripts/
│   ├── notes-cli.sh           # CLI wrapper for common operations
│   ├── health-check.sh        # Monitoring and alerting
│   ├── export-all.sh          # Full backup export
│   └── install.sh             # launchd deployment installer
├── tests/
│   ├── mocks/                 # Mock JXA client for CI (non-macOS)
│   └── unit/                  # Unit tests (vitest)
├── config/
│   ├── environments.json      # Account/folder per environment
│   └── launchd.plist          # Service definition template
└── package.json

Component Design

// src/notes-client.ts — Core abstraction over osascript
import { execSync } from "child_process";

export class NotesClient {
  private account: string;

  constructor(account = "iCloud") { this.account = account; }

  private exec(jxa: string): string {
    return execSync(`osascript -l JavaScript -e '${jxa.replace(/'/g, "'\\''")}'`,
      { encoding: "utf8", timeout: 30000 }).trim();
  }

  count(): number {
    return parseInt(this.exec(`Application("Notes").accounts().find(a => a.name() === "${this.account}").notes.length`));
  }

  list(): Array<{ id: string; title: string; modified: string }> {
    return JSON.parse(this.exec(`
      JSON.stringify(Application("Notes").accounts().find(a => a.name() === "${this.account}")
        .notes().map(n => ({id: n.id(), title: n.name(), modified: n.modificationDate().toISOString()})))
    `));
  }

  create(title: string, body: string, folder = "Notes"): string {
    return this.exec(`
      const Notes = Application("Notes");
      const acct = Notes.accounts().find(a => a.name() === "${this.account}");
      const f = acct.folders().find(f => f.name() === "${folder}") || acct.folders[0];
      const n = Notes.Note({name: "${title}", body: "${body}"});
      f.notes.push(n); n.id();
    `);
  }
}

Key Constraints

ConstraintImpactWorkaround
macOS onlyNo Linux/Windows serversRun on Mac; export data for cross-platform consumption
No REST APICannot access remotelyOptional: expose local HTTP server; lock down to localhost
iCloud sync lagWrites may take 5-30s to appear on other devicesPoll with delay; verify on target device
No webhooksCannot receive push notificationsPoll for changes every 60s; watch FSEvents on Notes DB
HTML-only bodyNo native Markdown supportConvert HTML to/from Markdown in export/import layer
No attachment export via JXABinary data inaccessible from scriptingUse Shortcuts for attachment extraction

Error Handling

IssueCauseSolution
Architecture requires macOS serverNo cloud-native optionDedicate a Mac mini as automation server; use Tailscale for remote access
Local HTTP API exposed to networkSecurity risk if not locked downBind to 127.0.0.1 only; use SSH tunnel for remote access
Cache out of sync with NotesPolling interval too longReduce poll interval; use FSEvents on NoteStore.sqlite for faster detection
Template HTML rejected by NotesInvalid HTML tagsTest templates with a canary note before bulk creation

Resources

Next Steps

For deploying this architecture as a service, see

apple-notes-deploy-integration
. For monitoring the running system, see
apple-notes-observability
.