A collection of Python CLIs for controlling Apple applications through AppleScript (osascript). No dependency on third-party Python packages, just the standard library. Each CLI is self-contained and can be installed separately, but they share a common design and codebase for AppleScript interaction.
mail-app is a Python CLI that controls Apple Mail that can view and send messages.
reminders-app is a companion Python CLI for Apple Reminders with list/reminder management and query-based bulk actions.
calendar-app is a Python CLI for Apple Calendar to manage calendars and events.
notes-app is a Python CLI for Apple Notes to manage notes and folders.
contacts-app is a Python CLI for macOS Contacts to manage contacts and groups.
- macOS
- Python 3.9+
To install the latest release from PyPI:
python3 -m pip install apple-cliTo install in editable mode for development:
git clone https://github.com/evilmarty/apple-cli.git
cd apple-cli
python3 -m pip install -e .After installation, the following commands will be available in your PATH:
mail-appreminders-appcalendar-appnotes-appcontacts-app
mail-app messages list [--account ACCOUNT] [--mailbox MAILBOX] [--limit N] [--order desc|asc] [--json]
mail-app messages view (--id "<id>" | --message-id "<message-id>") [--account ACCOUNT] [--mailbox MAILBOX] [--body-only] [--json]
mail-app messages show (--id "<id>" | --message-id "<message-id>") [--account ACCOUNT] [--mailbox MAILBOX]
mail-app messages send --to TO [--to TO] --subject "Hello" --body "Hi there" [--cc CC] [--bcc BCC] [--account ACCOUNT]
mail-app messages move (--id "<id>" | --message-id "<message-id>") --destination-mailbox DEST [--account ACCOUNT] [--source-mailbox MAILBOX]
mail-app messages archive (--id "<id>" | --message-id "<message-id>") [--account ACCOUNT] [--mailbox MAILBOX]
mail-app messages trash (--id "<id>" | --message-id "<message-id>") [--account ACCOUNT] [--mailbox MAILBOX]
mail-app messages spam (--id "<id>" | --message-id "<message-id>") [--account ACCOUNT] [--mailbox MAILBOX]mail-app mailboxes list [--account ACCOUNT] [--json]
mail-app mailboxes create --account ACCOUNT --name NAME [--parent-mailbox MAILBOX]
mail-app mailboxes rename --account ACCOUNT --mailbox MAILBOX --new-name NEW_NAME
mail-app mailboxes delete --account ACCOUNT --mailbox MAILBOXreminders-app reminders list [--list LIST] [--limit N] [--order desc|asc] [--completed|--all] [--json]
reminders-app reminders view --id ID [--body-only] [--json]
reminders-app reminders show --id ID
reminders-app reminders create --title TITLE [--notes TEXT] [--list LIST] [--due-date DATE] [--priority N]
reminders-app reminders update --id ID [--title TITLE] [--notes TEXT] [--due-date DATE] [--priority N] [--list LIST]
reminders-app reminders complete [--id ID | selectors | --all]
reminders-app reminders uncomplete [--id ID | selectors | --all]
reminders-app reminders delete [--id ID | selectors | --all]
reminders-app reminders move --destination-list LIST [--id ID | selectors | --all]reminders-app lists list [--json]
reminders-app lists create --name NAME
reminders-app lists rename --list NAME --new-name NEW_NAME
reminders-app lists delete --list NAMEDue to inefficiencies in how the Apple Reminders application handles bulk queries and property retrieval through AppleScript, some commands (especially list when querying many items) may experience delays or timeouts. If you encounter timeouts, try narrowing the scope of your command by specifying a --list.
calendar-app events list [--calendar CALENDAR] [--start-after DATE] [--start-before DATE] [--limit N] [--order desc|asc] [--json]
calendar-app events view --id ID [--json]
calendar-app events show --id ID
calendar-app events create --summary SUMMARY --start-date DATE (--end-date DATE | --duration DURATION) [--location LOC] [--notes TEXT] [--all-day] [--url URL] [--calendar CAL] [--alarm MIN]
calendar-app events update --id ID [--summary SUMMARY] [--start-date DATE] [--end-date DATE | --duration DURATION] [--location LOC] [--notes TEXT] [--all-day|--no-all-day] [--url URL] [--calendar CAL]
calendar-app events delete --id IDevents listdefaults to events starting from "now" if no date range is provided.events listoutput includes a human-friendlydurationcolumn (e.g.,15m,2h,1d).events create/update--durationaccepts human-friendly strings like30m,3h,2d.
calendar-app calendars list [--json]
calendar-app calendars create --name NAME
calendar-app calendars rename --calendar NAME --new-name NEW_NAME
calendar-app calendars delete --calendar NAMEnotes-app notes list [--folder FOLDER] [--limit N] [--order desc|asc] [--json]
notes-app notes view --id ID [--json]
notes-app notes show --id ID
notes-app notes create --name NAME --body TEXT [--folder FOLDER]
notes-app notes update --id ID [--name NAME] [--body TEXT] [--folder FOLDER]
notes-app notes delete --id IDnotes-app folders list [--json]
notes-app folders create --name NAME
notes-app folders delete --name NAMEcontacts-app contacts list [--search TEXT] [--group GROUP] [--limit N] [--order desc|asc] [--json]
contacts-app contacts view --id ID [--json]
contacts-app contacts show --id ID
contacts-app contacts create --first-name NAME [--last-name NAME] [--organization ORG] [--job-title JOB] [--nickname NICK] [--note TEXT] [--email EMAIL] [--phone PHONE]
contacts-app contacts delete --id IDcontacts-app groups list [--json]
contacts-app groups create --name NAME
contacts-app groups delete --name NAMETo build a wheel and source distribution:
-
Clone the repository:
git clone https://github.com/evilmarty/apple-cli.git cd apple-cli -
(Optional) Create and activate a virtual environment:
python3 -m venv .venv source .venv/bin/activate -
Run the build command:
make build
The build artifacts will be available in the dist/ directory.
The project uses the standard library unittest framework.
To run all tests:
make testTo run tests for a specific application:
PYTHONPATH=src python3 -m unittest tests/test_calendar_app.pyTests use mocking extensively to avoid actually executing AppleScripts that would interact with your native apps, making them safe to run in any environment.
Contributions are welcome! If you'd like to contribute:
- Report Bugs or Request Features: Open an issue on GitHub.
- Submit Pull Requests:
- Fork the repository.
- Create a new branch for your feature or fix.
- Ensure your code follows the existing style and is well-documented.
- Important: Add tests for any new functionality or bug fixes.
- Run the full test suite (
make test) before submitting.
- Coding Standards:
- Use type hints for all function signatures.
- Keep the codebase compatible with Python 3.9+.
- Maintain the zero-dependency goal (use only the Python standard library).