Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,4 @@ Batch commands (`batch-read`, `batch-move`, `batch-delete`, `batch-flag`) log th

Tests live in `tests/` and use `unittest.mock` to mock AppleScript calls. No actual Mail.app interaction happens during testing. Run with `pytest --cov` for coverage.

The suite has 675 tests (100% coverage) across 19 test files covering command parsing, AppleScript output parsing, error paths, date handling, formatting, config resolution, batch operations, undo logging, templates, AI classification logic, unsubscribe HTTP paths, Todoist integration, inbox tools, and bulk export. Six unreachable defensive guards are marked with `# pragma: no cover`.
The suite has 678 tests (100% coverage) across 19 test files covering command parsing, AppleScript output parsing, error paths, date handling, formatting, config resolution, batch operations, undo logging, templates, AI classification logic, unsubscribe HTTP paths, Todoist integration, inbox tools, bulk export, and the public API module. Six unreachable defensive guards are marked with `# pragma: no cover`.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Added

- **Public Python API** — new `mxctl.api` module with 56 importable functions for programmatic access to Apple Mail. All command internals refactored to separate data retrieval from CLI presentation. External projects can now `from mxctl.api import get_messages, read_message` without any CLI or argparse dependency.

## [0.4.0] - 2026-02-25

### Added
Expand Down
91 changes: 19 additions & 72 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

> Apple Mail from your terminal.

**50 commands.** Triage with AI, batch-process newsletters, turn emails into Todoist tasks — all from the terminal. Every command supports `--json` for scripting and AI workflows. Zero external dependencies.
**50+ commands.** Triage with AI, batch-process newsletters, turn emails into Todoist tasks — all from the terminal. Every command supports `--json` for scripting and AI workflows. Zero external dependencies.

<p align="center">
<img src="demo/demo.gif" alt="mxctl demo — inbox, triage, summary, and batch operations" width="700">
Expand All @@ -39,7 +39,7 @@

## Key Features

- **50 Commands** - Everything from basic operations to advanced batch processing
- **50+ Commands** - Everything from basic operations to advanced batch processing
- **Any Account, One Interface** - iCloud, Gmail, Outlook, Exchange, IMAP -- whatever Mail.app has, this works with
- **Gmail Mailbox Translation** - Automatically maps standard names (`Trash`, `Spam`, `Sent`) to Gmail's `[Gmail]/...` paths
- **Built for AI Workflows** - Every command supports `--json` output designed for AI assistants to read and act on
Expand Down Expand Up @@ -103,7 +103,7 @@ Inbox Overview
------------------------------------------
iCloud 3 unread (47 total)
Work Email 12 unread (203 total)
Johnny.Coats84@gmail.com 0 unread (18 total)
Personal Gmail 0 unread (18 total)
------------------------------------------
Total 15 unread
```
Expand Down Expand Up @@ -282,13 +282,6 @@ mxctl move 3 --to Archive # Move message [3]

Aliases update each time you run a listing command (`list`, `inbox`, `search`, `triage`, `summary`, etc.). Full message IDs still work if you prefer them. JSON output includes both `id` (real) and `alias` (short number).

### JSON Output for Automation
```bash
# Every command supports --json
mxctl inbox --json | jq '.accounts[0].unread_count'
mxctl search "invoice" --json | jq '.[].subject'
```

### Export Messages
```bash
# Export a single message
Expand All @@ -301,8 +294,6 @@ mxctl export "Work" --to ~/Documents/mail/ -a "Work Email"
mxctl export "INBOX" --to ~/Documents/mail/ -a "iCloud" --after 2026-01-01
```

Note: The destination flag is `--to` (not `--dest`).

### Email Templates
```bash
# Create a template
Expand Down Expand Up @@ -330,66 +321,27 @@ It walks you through selecting your AI assistant (Claude Code, Cursor, or Windsu

#### Manual setup

If you prefer to set things up yourself, add this block to your assistant's context file (`~/.claude/CLAUDE.md` for Claude Code, `.cursorrules` for Cursor, `.windsurfrules` for Windsurf):

````markdown
## mxctl — Apple Mail CLI

`mxctl` manages Apple Mail from the terminal. Use it to read, triage, and act on email without opening Mail.app.

Key commands:
- `mxctl inbox` — unread counts across all accounts
- `mxctl triage` — categorize unread mail by urgency
- `mxctl summary` — concise one-liner per unread message
- `mxctl list [-a ACCOUNT] [--unread] [--limit N]` — list messages
- `mxctl read ID [-a ACCOUNT] [-m MAILBOX]` — read a message
- `mxctl search QUERY [--sender]` — search messages
- `mxctl mark-read ID` / `mxctl flag ID` — message actions
- `mxctl batch-move --from-sender ADDR --to-mailbox MAILBOX` — bulk move
- `mxctl batch-delete --older-than DAYS -m MAILBOX` — bulk delete
- `mxctl undo` — roll back the last batch operation
- `mxctl to-todoist ID --project NAME` — turn an email into a task

Add `--json` to any command for structured output. Run `mxctl --help` for all 50 commands.
Default account is set in `~/.config/mxctl/config.json`. Use `-a "Account Name"` to switch accounts.
````
Prefer to configure it yourself? Run `mxctl ai-setup --print` to get the raw snippet, then paste it into your assistant's context file (`~/.claude/CLAUDE.md`, `.cursorrules`, `.windsurfrules`, etc.).

#### Local AI (Ollama, LM Studio, Aider, etc.)

For local models, use `--print` to dump the raw snippet and pipe it wherever you need:
Use `--print` to get the raw snippet for piping:

```bash
# Copy to clipboard
mxctl ai-setup --print | pbcopy

# Append to an Ollama Modelfile
mxctl ai-setup --print >> ~/Modelfile

# Save as a reusable system prompt file
mxctl ai-setup --print > ~/.config/mxctl-prompt.md

# Pass to Aider
mxctl ai-setup --print > .aider.prompt.md
mxctl ai-setup --print | pbcopy # clipboard
mxctl ai-setup --print >> ~/Modelfile # Ollama
mxctl ai-setup --print > .aider.prompt.md # Aider
```

`--print` outputs clean markdown with no interactive prompts — it's designed for piping.
For a one-off session, `mxctl --help` gives any AI the full command reference.

#### Ad-hoc: inject the full command list on demand

For a one-off session with any AI tool, paste the full command reference directly into the chat:
### With Claude Code

```bash
mxctl --help
```
Just ask in natural language:

The output is concise enough to fit in any context window and gives the AI everything it needs to pick the right command.

### With Claude Code
```bash
# Just ask Claude to check your mail
"Run mxctl triage and tell me what's urgent"
"Summarize my unread mail and create Todoist tasks for anything that needs action"
```
> *"Run mxctl triage and tell me what's urgent"*
>
> *"Summarize my unread mail and create Todoist tasks for anything that needs action"*

### With any AI tool
```bash
Expand All @@ -405,12 +357,11 @@ mxctl triage --json | llm "Draft responses for the urgent items"
# Unread count for your status bar
mxctl count

# Export to JSON for any workflow
mxctl inbox --json | jq '.accounts[].unread_count'
# Every command supports --json
mxctl inbox --json | jq '.[].unread'
mxctl search "invoice" --json | jq '.[].subject'
```

The CLI is the bridge between Mail.app and whatever tools you use -- AI, scripts, or both.

## AI Demos

These demos show how an AI assistant (like Claude Code) uses mxctl to manage your inbox conversationally. You say what you want in plain English, and the AI picks the right commands, checks before acting, and reports back.
Expand Down Expand Up @@ -443,7 +394,7 @@ The AI analyzes which newsletters you actually read vs. ignore, then unsubscribe

Built with modern Python patterns:
- **Zero runtime dependencies** (stdlib only)
- **Comprehensive test suite** (675 tests)
- **Comprehensive test suite** (678 tests)
- **Modular command structure** (16 focused modules)
- **AppleScript bridge** for Mail.app communication
- **Three-tier account resolution** (explicit flag -> config default -> last-used)
Expand All @@ -463,7 +414,7 @@ See [ARCHITECTURE.md](ARCHITECTURE.md) for detailed architecture documentation.
| **Setup** | `pip install mxctl && mxctl init` | Extensive config | OAuth flows + API keys | Write your own scripts |
| **Dependencies** | Zero (stdlib only) | Varies | SDK + auth libraries | None |

**In short:** mutt replaces Mail.app (you lose macOS integration). Provider APIs lock you into one service. Raw AppleScript works but you're building everything from scratch. mxctl gives you 50 structured commands on top of the Mail.app you already use.
**In short:** mutt replaces Mail.app (you lose macOS integration). Provider APIs lock you into one service. Raw AppleScript works but you're building everything from scratch. mxctl gives you 50+ structured commands on top of the Mail.app you already use.

## Contributing

Expand All @@ -473,10 +424,6 @@ Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

MIT License - see [LICENSE](LICENSE) file for details.

## Acknowledgments

Built to automate email workflows without leaving the terminal.

## Contact

- **GitHub:** [@Jscoats](https://github.com/Jscoats)
Expand Down
188 changes: 188 additions & 0 deletions src/mxctl/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
"""Public Python API for mxctl.

Import data functions for programmatic access to Apple Mail.
CLI behavior is unchanged — this module provides the same data
without formatting or printing.

Usage:
from mxctl.api import list_messages, read_message
messages = get_messages(account="iCloud", mailbox="INBOX")
"""

# --- Account & Mailbox ---
from mxctl.commands.mail.accounts import (
get_accounts,
get_inbox_summary,
get_mailboxes,
get_unread_count,
)

# --- Actions ---
from mxctl.commands.mail.actions import (
delete_message,
mark_junk,
move_message,
not_junk,
open_message,
set_flag_status,
set_read_status,
)

# --- AI / Smart Commands ---
from mxctl.commands.mail.ai import (
find_related,
get_context,
get_summary,
get_triage,
)

# --- Analytics ---
from mxctl.commands.mail.analytics import (
get_digest,
get_flagged_messages,
get_stats,
get_top_senders,
)

# --- Attachments ---
from mxctl.commands.mail.attachments import (
get_attachments,
save_attachment,
)

# --- Batch Operations ---
from mxctl.commands.mail.batch import (
batch_delete,
batch_flag,
batch_move,
batch_read,
)

# --- Compose ---
from mxctl.commands.mail.compose import create_draft

# --- Composite ---
from mxctl.commands.mail.composite import (
create_forward,
create_reply,
export_message,
export_messages,
get_thread,
)

# --- Inbox Tools ---
from mxctl.commands.mail.inbox_tools import (
get_inbox_categories,
get_newsletter_senders,
get_weekly_review,
)

# --- Mailbox Management ---
from mxctl.commands.mail.manage import (
create_mailbox,
delete_mailbox,
empty_trash,
)

# --- Messages ---
from mxctl.commands.mail.messages import (
get_messages,
read_message,
search_messages,
)

# --- System ---
from mxctl.commands.mail.system import (
check_mail_status,
get_headers,
get_raw_headers,
get_rules,
toggle_rule,
)

# --- Templates ---
from mxctl.commands.mail.templates import (
create_template,
delete_template,
get_template,
get_templates,
)

# --- Todoist Integration ---
from mxctl.commands.mail.todoist_integration import create_todoist_task

# --- Undo ---
from mxctl.commands.mail.undo import (
list_undo_history,
undo_last,
)

__all__ = [
# Account & Mailbox
"get_accounts",
"get_inbox_summary",
"get_mailboxes",
"get_unread_count",
# Messages
"get_messages",
"read_message",
"search_messages",
# Actions
"delete_message",
"mark_junk",
"move_message",
"not_junk",
"open_message",
"set_flag_status",
"set_read_status",
# Attachments
"get_attachments",
"save_attachment",
# Compose
"create_draft",
# Templates
"create_template",
"delete_template",
"get_template",
"get_templates",
# Batch Operations
"batch_delete",
"batch_flag",
"batch_move",
"batch_read",
# Analytics
"get_digest",
"get_flagged_messages",
"get_stats",
"get_top_senders",
# AI / Smart Commands
"find_related",
"get_context",
"get_summary",
"get_triage",
# Inbox Tools
"get_inbox_categories",
"get_newsletter_senders",
"get_weekly_review",
# Composite
"create_forward",
"create_reply",
"export_message",
"export_messages",
"get_thread",
# Mailbox Management
"create_mailbox",
"delete_mailbox",
"empty_trash",
# System
"check_mail_status",
"get_headers",
"get_raw_headers",
"get_rules",
"toggle_rule",
# Undo
"list_undo_history",
"undo_last",
# Todoist Integration
"create_todoist_task",
]
Loading