- Automating macOS Apps (Apple Events, AppleScript, JXA)
- Technology Status
- JXA and AppleScript are legacy (last major updates: 2015-2016). Modern alternatives:
- PyXA
-
- Active Python automation (see installation below)
- Shortcuts App
-
- Visual workflow builder
- Swift/Objective-C
-
- Production-ready automation
- macOS Sequoia 15 Notes
- Test scripts on target macOS due to:
- Stricter TCC permissions
- Enhanced Apple Events security
- Sandbox improvements
- Core framing (use this mental model)
- Apple Events
-
- The underlying inter-process communication (IPC) transport for macOS automation.
- AppleScript
-
- The original DSL (Domain-Specific Language) for Apple Events: best for discovery, dictionaries, and quick prototypes.
- JXA (JavaScript for Automation)
-
- A JavaScript binding to the Apple Events layer: best for data handling, JSON, and maintainable logic.
- ObjC Bridge
-
- JavaScript access to Objective-C frameworks (Foundation, AppKit) for advanced macOS capabilities beyond app dictionaries.
- When to use which
- Use
- AppleScript
- for discovery, dictionary exploration, UI scripting, and quick one-offs.
- Use
- JXA
- for robust logic, JSON pipelines, integration with Python/Node, and long-lived automation.
- Hybrid pattern (recommended): discover in AppleScript, implement in JXA for production use.
- When to use this skill
- Foundation for app-specific skills (Calendar, Notes, Mail, Keynote, Excel, Reminders).
- Run the automation warm-up scripts before first automation to surface macOS permission prompts.
- Use as the base reference for permissions, shell integration, and UI scripting fallbacks.
- Workflow (default)
- Identify target app + dictionary
- using Script Editor.
- Prototype
- a minimal command that works.
- Example
- :
- tell application "Finder" to get name of first item in desktop
- Decide language
- using the rules above.
- Harden
-
- add error handling, timeouts, and permission checks.
- JXA
- :
- try { ... } catch (e) { console.log('Error:', e.message); }
- AppleScript
- :
- try ... on error errMsg ... end try
- Validate
- run a read-only command and check outputs.
Example
:
osascript -e 'tell application "Finder" to get name of home'
Confirm: Output matches expected (e.g., user home folder name)
Integrate
via
osascript
for CLI or pipeline use.
UI scripting fallback
only when the dictionary is missing/incomplete.
Example UI Script
:
tell application "System Events" to click button 1 of window 1 of process "App"
Example JXA
:
Application('System Events').processes.byName('App').windows[0].buttons[0].click()
Validation Checklist
Automation/Accessibility permissions granted (System Settings > Privacy & Security)
App is running:
Application("App").running()
returns true
State checked before acting (e.g., folder exists)
Dictionary method used (not UI scripting)
Delays/retries added for UI operations
Read-only test command succeeds
Output matches expected values
Automation permission warm-up
Use before first automation run or after macOS updates to surface prompts:
All apps at once:
skills/automating-mac-apps/scripts/request_automation_permissions.sh
(or
.py
).
Per-app:
skills/automating-
/scripts/set_up_ _automation.{sh,py} (calendar, notes, mail, keynote, excel, reminders). Voice Memos (no dictionary): skills/automating-voice-memos/scripts/set_up_voice_memos_automation.sh to activate app + check data paths; enable Accessibility + consider Full Disk Access. Run from the same host you intend to automate with (Terminal vs Python) so the correct app gets Automation approval. Each script runs read-only AppleScript calls (list accounts/calendars/folders, etc.) to request Terminal/Python control; click “Allow” when prompted. Modern Python Alternatives to JXA PyXA (Python for macOS Automation) - Preferred for new projects: PyXA Features Active development (v0.2.3+), modern Python syntax App automation: Safari, Calendar, Reminders, Mail, Music UI scripting, clipboard, notifications, AppleScript integration Method chaining: app.lists().reminders().title() PyXA Installation {#pyxa-installation}
Install PyXA
pip install mac-pyxa
Or with pip3 explicitly
pip3 install mac-pyxa
Requirements:
- Python 3.10+ (check with: python3 --version)
- macOS 12+ (Monterey or later recommended)
- PyObjC is installed automatically as a dependency
Verify installation
python3 -c "import PyXA; print(f'PyXA {PyXA.version} installed successfully')" Note: All app-specific skills in this plugin that show PyXA examples assume PyXA is installed. See this section for installation. PyXA Example (Safari Automation) import PyXA
Launch Safari and navigate
safari
PyXA . Safari ( ) safari . activate ( ) safari . open_location ( "https://example.com" )
Get current tab URL
current_url
safari . current_tab . url print ( f"Current URL: { current_url } " ) PyXA Example (Reminders) import PyXA reminders = PyXA . Reminders ( ) work_list = reminders . lists ( ) . by_name ( "Work" )
Add new reminder
new_reminder
- work_list
- .
- reminders
- (
- )
- .
- push
- (
- {
- "name"
- :
- "Review PyXA documentation"
- ,
- "body"
- :
- "Explore modern macOS automation options"
- }
- )
- PyXA Official Resources
- :
- Documentation:
- https://skaplanofficial.github.io/PyXA/
- GitHub:
- https://github.com/SKaplanOfficial/PyXA
- Community: Active Discord and GitHub discussions
- PyObjC (Python-Objective-C Bridge)
- - For Low-Level macOS Integration:
- PyObjC Capabilities
- Direct Framework Access
-
- AppKit, Foundation, and all macOS frameworks
- Apple Events
-
- Send Apple Events via Scripting Bridge
- Script Execution
-
- Run AppleScript or JXA from Python
- System APIs
- Direct access to CalendarStore, AddressBook, SystemEvents Installation pip install pyobjc
Installs bridges for major frameworks
PyObjC Example (AppleScript Execution) from Foundation import NSAppleScript
Execute AppleScript from Python
script_source
''' tell application "Safari" return URL of current tab end tell ''' script = NSAppleScript . alloc ( ) . initWithSource_ ( script_source ) result , error = script . executeAndReturnError_ ( None ) if error : print ( f"Error: { error } " ) else : print ( f"Current Safari URL: { result . stringValue ( ) } " ) PyObjC Example (App Control via Scripting Bridge) from ScriptingBridge import SBApplication
Control Mail app
SBApplication . applicationWithBundleIdentifier_ ( "com.apple.Mail" ) inbox = mail . inboxes ( ) [ 0 ]
Access first inbox
Get unread message count
unread_count
inbox . unreadCount ( ) print ( f"Unread messages: { unread_count } " ) PyObjC Official Resources : Documentation: https://pyobjc.readthedocs.io Examples: Extensive GitHub repositories with code samples JXA Status JXA has no updates since 2016. Use PyXA for new projects when possible. When Not to Use Cross-platform automation (use Selenium/Playwright for web) Full UI testing (use XCUITest or Appium) Environments blocking Automation/Accessibility permissions Non-macOS platforms Simple shell scripting tasks (use Bash directly)