Claude-code-plugins-plus apple-notes-ci-integration
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-ci-integration" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-apple-notes-ci-integration && rm -rf "$T"
manifest:
plugins/saas-packs/apple-notes-pack/skills/apple-notes-ci-integration/SKILL.mdsource content
Apple Notes CI Integration
Overview
Apple Notes automation is macOS-only because it depends on the Apple Events subsystem and Notes.app. CI pipelines must use GitHub Actions macOS runners (
macos-latest or macos-14). However, macOS CI runners have restricted TCC (Transparency, Consent, and Control) permissions, which means direct Notes.app automation via osascript will fail in CI. The standard pattern is to run unit tests against a mock JXA client in CI, and reserve real Notes.app integration tests for local macOS machines or self-hosted runners with pre-granted automation permissions.
GitHub Actions Workflow
# .github/workflows/notes-ci.yml name: Notes Automation CI on: push: branches: [main] pull_request: branches: [main] jobs: unit-tests: runs-on: macos-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: "20", cache: "npm" } - run: npm ci - name: Verify macOS version run: sw_vers - name: Lint JXA scripts run: | # Validate JavaScript syntax in all .jxa files for f in scripts/*.jxa; do node --check "$f" 2>/dev/null || echo "WARN: $f is osascript-only" done - name: Unit tests (mocked Notes client) run: npm test - name: Validate JXA templates run: | # Ensure osascript can parse (but not execute) JXA scripts for f in scripts/*.jxa; do osascript -l JavaScript -e "$(cat "$f")" 2>&1 | grep -v "Not authorized" || true done
Mock Client for CI
// tests/mocks/notes-client.mock.ts export class MockAppleNotesClient { private notes: Array<{ id: string; title: string; body: string; folder: string }> = []; createNote(title: string, body: string, folder = "Notes"): string { const id = `mock-note-${Date.now()}-${Math.random().toString(36).slice(2)}`; this.notes.push({ id, title, body, folder }); return id; } listNotes() { return [...this.notes]; } getNote(id: string) { return this.notes.find(n => n.id === id) || null; } searchNotes(q: string) { return this.notes.filter(n => n.title.includes(q) || n.body.includes(q)); } deleteNote(id: string) { this.notes = this.notes.filter(n => n.id !== id); } getFolders() { return [...new Set(this.notes.map(n => n.folder))]; } }
Self-Hosted Runner with TCC Pre-Approval
# On a self-hosted macOS runner, pre-grant automation permissions: # 1. Open System Settings > Privacy & Security > Automation # 2. Grant your CI user's terminal access to Notes.app # 3. Verify with: osascript -l JavaScript -e 'Application("Notes").defaultAccount.notes.length' # For headless runners, use tccutil (requires SIP adjustment or MDM profile): # sudo tccutil --insert com.apple.Notes --service AppleEvents --app /usr/bin/osascript
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| "Not authorized to send Apple events" in CI | TCC blocks automation on CI runners | Use mock client; real tests on self-hosted runner |
syntax errors not caught | JXA has no standalone linter | Use for JS syntax; parse-only validation |
Flaky tests on | Runner image updates change Notes state | Pin to ; always use mocked client |
| Tests pass locally, fail in CI | Different macOS version or missing app | Check output; ensure Notes.app exists on runner |
| Timeout waiting for Notes.app | App launch delay on cold runner | Add before osascript calls |
Resources
Next Steps
For diagnosing CI failures, see
apple-notes-common-errors. For production deployment of automation scripts, see apple-notes-deploy-integration.