mxctl 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. mxctl-0.3.0/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
  2. mxctl-0.3.0/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
  3. mxctl-0.3.0/.github/PULL_REQUEST_TEMPLATE.md +19 -0
  4. mxctl-0.3.0/.github/dependabot.yml +10 -0
  5. mxctl-0.3.0/.github/workflows/ci.yml +31 -0
  6. mxctl-0.3.0/.github/workflows/release.yml +36 -0
  7. mxctl-0.3.0/.gitignore +42 -0
  8. mxctl-0.3.0/.pre-commit-config.yaml +7 -0
  9. mxctl-0.3.0/ARCHITECTURE.md +90 -0
  10. mxctl-0.3.0/CHANGELOG.md +73 -0
  11. mxctl-0.3.0/CODE_OF_CONDUCT.md +39 -0
  12. mxctl-0.3.0/CONTRIBUTING.md +144 -0
  13. mxctl-0.3.0/LICENSE +21 -0
  14. mxctl-0.3.0/PKG-INFO +439 -0
  15. mxctl-0.3.0/README.md +407 -0
  16. mxctl-0.3.0/SECURITY.md +32 -0
  17. mxctl-0.3.0/demo/ai-demo.gif +0 -0
  18. mxctl-0.3.0/demo/batch-delete-demo.gif +0 -0
  19. mxctl-0.3.0/demo/demo.gif +0 -0
  20. mxctl-0.3.0/demo/demo.tape +41 -0
  21. mxctl-0.3.0/demo/init-demo.gif +0 -0
  22. mxctl-0.3.0/demo/unsubscribe-demo.gif +0 -0
  23. mxctl-0.3.0/pyproject.toml +62 -0
  24. mxctl-0.3.0/src/mxctl/__init__.py +3 -0
  25. mxctl-0.3.0/src/mxctl/__main__.py +5 -0
  26. mxctl-0.3.0/src/mxctl/commands/__init__.py +0 -0
  27. mxctl-0.3.0/src/mxctl/commands/mail/__init__.py +1 -0
  28. mxctl-0.3.0/src/mxctl/commands/mail/accounts.py +347 -0
  29. mxctl-0.3.0/src/mxctl/commands/mail/actions.py +593 -0
  30. mxctl-0.3.0/src/mxctl/commands/mail/ai.py +355 -0
  31. mxctl-0.3.0/src/mxctl/commands/mail/analytics.py +390 -0
  32. mxctl-0.3.0/src/mxctl/commands/mail/attachments.py +156 -0
  33. mxctl-0.3.0/src/mxctl/commands/mail/batch.py +402 -0
  34. mxctl-0.3.0/src/mxctl/commands/mail/compose.py +133 -0
  35. mxctl-0.3.0/src/mxctl/commands/mail/composite.py +430 -0
  36. mxctl-0.3.0/src/mxctl/commands/mail/inbox_tools.py +502 -0
  37. mxctl-0.3.0/src/mxctl/commands/mail/manage.py +185 -0
  38. mxctl-0.3.0/src/mxctl/commands/mail/messages.py +363 -0
  39. mxctl-0.3.0/src/mxctl/commands/mail/setup.py +362 -0
  40. mxctl-0.3.0/src/mxctl/commands/mail/system.py +202 -0
  41. mxctl-0.3.0/src/mxctl/commands/mail/templates.py +145 -0
  42. mxctl-0.3.0/src/mxctl/commands/mail/todoist_integration.py +145 -0
  43. mxctl-0.3.0/src/mxctl/commands/mail/undo.py +323 -0
  44. mxctl-0.3.0/src/mxctl/config.py +227 -0
  45. mxctl-0.3.0/src/mxctl/main.py +89 -0
  46. mxctl-0.3.0/src/mxctl/util/__init__.py +0 -0
  47. mxctl-0.3.0/src/mxctl/util/applescript.py +139 -0
  48. mxctl-0.3.0/src/mxctl/util/applescript_templates.py +185 -0
  49. mxctl-0.3.0/src/mxctl/util/dates.py +54 -0
  50. mxctl-0.3.0/src/mxctl/util/formatting.py +52 -0
  51. mxctl-0.3.0/src/mxctl/util/mail_helpers.py +192 -0
  52. mxctl-0.3.0/tests/__init__.py +0 -0
  53. mxctl-0.3.0/tests/conftest.py +43 -0
  54. mxctl-0.3.0/tests/test_ai_classification.py +413 -0
  55. mxctl-0.3.0/tests/test_applescript.py +126 -0
  56. mxctl-0.3.0/tests/test_commands.py +1607 -0
  57. mxctl-0.3.0/tests/test_compose_batch_errors.py +552 -0
  58. mxctl-0.3.0/tests/test_config.py +93 -0
  59. mxctl-0.3.0/tests/test_count.py +102 -0
  60. mxctl-0.3.0/tests/test_dates.py +94 -0
  61. mxctl-0.3.0/tests/test_error_paths.py +488 -0
  62. mxctl-0.3.0/tests/test_formatting.py +111 -0
  63. mxctl-0.3.0/tests/test_mail_helpers.py +82 -0
  64. mxctl-0.3.0/tests/test_manage.py +280 -0
  65. mxctl-0.3.0/tests/test_new_coverage.py +1169 -0
  66. mxctl-0.3.0/tests/test_resolve_context.py +86 -0
  67. mxctl-0.3.0/tests/test_setup.py +275 -0
  68. mxctl-0.3.0/tests/test_stats_undo.py +461 -0
  69. mxctl-0.3.0/tests/test_templates.py +188 -0
  70. mxctl-0.3.0/tests/test_wave1_coverage.py +889 -0
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: Bug Report
3
+ about: Report a bug or unexpected behavior
4
+ title: '[BUG] '
5
+ labels: bug
6
+ assignees: ''
7
+ ---
8
+
9
+ ## Bug Description
10
+ <!-- A clear description of what the bug is -->
11
+
12
+ ## Steps to Reproduce
13
+ 1. Run command: `mxctl ...`
14
+ 2. See error: ...
15
+ 3. ...
16
+
17
+ ## Expected Behavior
18
+ <!-- What you expected to happen -->
19
+
20
+ ## Actual Behavior
21
+ <!-- What actually happened -->
22
+
23
+ ## Environment
24
+ - macOS version:
25
+ - Python version: (`python3 --version`)
26
+ - mxctl version:
27
+ - Mail.app version:
28
+
29
+ ## Error Messages
30
+ ```
31
+ <!-- Paste any error messages or stack traces here -->
32
+ ```
33
+
34
+ ## Additional Context
35
+ <!-- Any other relevant information -->
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: Feature Request
3
+ about: Suggest a new feature or enhancement
4
+ title: '[FEATURE] '
5
+ labels: enhancement
6
+ assignees: ''
7
+ ---
8
+
9
+ ## Feature Description
10
+ <!-- Clear description of the feature you'd like to see -->
11
+
12
+ ## Why is this useful?
13
+ <!-- Explain the use case and who would benefit -->
14
+
15
+ ## Example Usage
16
+ <!-- Show how you envision using this feature -->
17
+ ```bash
18
+ mxctl ...
19
+ ```
20
+
21
+ ## Alternatives Considered
22
+ <!-- Are there workarounds or alternative approaches? -->
23
+
24
+ ## Additional Context
25
+ <!-- Any other relevant information, mockups, or examples -->
@@ -0,0 +1,19 @@
1
+ ## What does this PR do?
2
+
3
+ <!-- Brief description of the change -->
4
+
5
+ ## Why is this needed?
6
+
7
+ <!-- What problem does it solve? Link to related issue if applicable -->
8
+
9
+ ## How was this tested?
10
+
11
+ - [ ] Existing tests pass (`pytest`)
12
+ - [ ] New tests added (if applicable)
13
+ - [ ] Manually tested on macOS with Apple Mail
14
+
15
+ ## Checklist
16
+
17
+ - [ ] Code follows existing patterns in the codebase
18
+ - [ ] No external runtime dependencies added
19
+ - [ ] Documentation updated (if user-facing change)
@@ -0,0 +1,10 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: "pip"
4
+ directory: "/"
5
+ schedule:
6
+ interval: "weekly"
7
+ - package-ecosystem: "github-actions"
8
+ directory: "/"
9
+ schedule:
10
+ interval: "weekly"
@@ -0,0 +1,31 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.12"
17
+ - run: pip install ruff
18
+ - run: ruff check src/ tests/
19
+
20
+ test:
21
+ runs-on: macos-latest
22
+ strategy:
23
+ matrix:
24
+ python-version: ["3.10", "3.11", "3.12"]
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - uses: actions/setup-python@v5
28
+ with:
29
+ python-version: ${{ matrix.python-version }}
30
+ - run: pip install -e ".[dev]"
31
+ - run: pytest --cov --cov-report=term-missing
@@ -0,0 +1,36 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ contents: write
10
+
11
+ jobs:
12
+ release:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Extract version from tag
18
+ id: version
19
+ run: echo "version=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
20
+
21
+ - name: Extract changelog for this version
22
+ id: changelog
23
+ run: |
24
+ # Extract the section for this version from CHANGELOG.md
25
+ version="${GITHUB_REF#refs/tags/v}"
26
+ awk "/^## \\[${version}\\]/{found=1; next} /^## \\[/{if(found) exit} found{print}" CHANGELOG.md > /tmp/release-notes.md
27
+ echo "Release notes:"
28
+ cat /tmp/release-notes.md
29
+
30
+ - name: Create GitHub Release
31
+ uses: softprops/action-gh-release@v2
32
+ with:
33
+ name: ${{ steps.version.outputs.version }}
34
+ body_path: /tmp/release-notes.md
35
+ draft: false
36
+ prerelease: ${{ contains(github.ref, 'rc') || contains(github.ref, 'beta') }}
mxctl-0.3.0/.gitignore ADDED
@@ -0,0 +1,42 @@
1
+ # Python
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .venv/
9
+ venv/
10
+ .pytest_cache/
11
+
12
+ # Config files (may contain tokens/personal data)
13
+ config.json
14
+ state.json
15
+ mail-templates.json
16
+ mail-undo.json
17
+
18
+ # macOS
19
+ .DS_Store
20
+ **/.DS_Store
21
+
22
+ # Editor
23
+ .vscode/
24
+ .idea/
25
+ *.swp
26
+ *.swo
27
+ *~
28
+
29
+ # Launch materials (not for public repo)
30
+ REDDIT_LAUNCH.md
31
+
32
+ # Local development instructions (personal, not for public repo)
33
+ CLAUDE.md
34
+
35
+ # Demo build artifacts (GIFs are committed, scripts are not)
36
+ demo/*.cast
37
+ demo/*.sh
38
+ demo/bin/
39
+ .coverage
40
+
41
+ # Lock files
42
+ uv.lock
@@ -0,0 +1,7 @@
1
+ repos:
2
+ - repo: https://github.com/astral-sh/ruff-pre-commit
3
+ rev: v0.9.7
4
+ hooks:
5
+ - id: ruff
6
+ args: [--fix]
7
+ - id: ruff-format
@@ -0,0 +1,90 @@
1
+ # Architecture
2
+
3
+ This document explains the high-level design of mxctl for contributors.
4
+
5
+ ## Overview
6
+
7
+ mxctl is a Python CLI that controls Apple Mail via AppleScript. It uses zero external runtime dependencies -- everything is built on Python's standard library.
8
+
9
+ ```
10
+ User -> CLI (argparse) -> Command Module -> AppleScript Bridge -> Mail.app
11
+ ```
12
+
13
+ ## Directory Structure
14
+
15
+ ```
16
+ src/mxctl/
17
+ ├── main.py # Top-level argparse router
18
+ ├── config.py # Constants, account resolution, validation
19
+ ├── util/
20
+ │ ├── applescript.py # run(), escape(), sanitize_path()
21
+ │ ├── applescript_templates.py # Reusable AppleScript patterns
22
+ │ ├── formatting.py # format_output(), truncate(), die()
23
+ │ ├── mail_helpers.py # resolve_message_context(), normalize_subject()
24
+ │ └── dates.py # parse_date(), to_applescript_date()
25
+ └── commands/
26
+ └── mail/
27
+ ├── __init__.py # Package marker
28
+ ├── accounts.py # inbox, accounts, mailboxes
29
+ ├── messages.py # list, read, search
30
+ ├── actions.py # mark-read, mark-unread, flag, unflag, move, delete
31
+ ├── compose.py # draft (supports --template)
32
+ ├── attachments.py # attachments, save-attachment
33
+ ├── manage.py # create-mailbox, delete-mailbox, empty-trash
34
+ ├── batch.py # batch-read, batch-flag, batch-move, batch-delete
35
+ ├── analytics.py # stats, top-senders, digest, show-flagged
36
+ ├── setup.py # init (first-time setup wizard)
37
+ ├── system.py # check, headers, rules, junk, not-junk
38
+ ├── composite.py # export, thread, reply, forward
39
+ ├── ai.py # summary, triage, context, find-related
40
+ ├── templates.py # templates list/create/show/delete
41
+ ├── todoist_integration.py # to-todoist
42
+ ├── inbox_tools.py # process-inbox, clean-newsletters, weekly-review
43
+ └── undo.py # undo, undo --list
44
+ ```
45
+
46
+ ## AppleScript Bridge
47
+
48
+ All Mail.app interaction goes through `util/applescript.py`, which wraps `osascript -e`. This module provides:
49
+
50
+ - **`run(script, timeout=30)`** -- Execute an AppleScript string and return stdout
51
+ - **`escape(string)`** -- Sanitize strings for safe embedding in AppleScript
52
+ - **`sanitize_path(path)`** -- Expand and resolve file paths
53
+
54
+ AppleScript returns multi-field data using `FIELD_SEPARATOR` (ASCII Unit Separator `\x1f`) and `RECORD_SEPARATOR` (ASCII Record Separator `\x1e`), defined in `config.py`. Command modules split on these to parse structured responses from Mail.app.
55
+
56
+ Reusable AppleScript patterns (inbox iteration, message lookup) live in `applescript_templates.py` to avoid duplication across commands.
57
+
58
+ ## Three-Tier Account Resolution
59
+
60
+ When a command needs a mail account, resolution follows this priority:
61
+
62
+ 1. **Explicit flag** -- `-a "Account Name"` on the command line
63
+ 2. **Config default** -- `default_account` in `~/.config/mxctl/config.json`
64
+ 3. **Last-used** -- Most recently used account stored in `state.json`
65
+
66
+ This is implemented in `config.py:resolve_account()` and used by `util/mail_helpers.py:resolve_message_context()`.
67
+
68
+ ## Command Registration
69
+
70
+ Each command module in `commands/mail/` exports a `register(subparsers)` function that adds its commands to the argparse tree. `main.py` imports and calls all `register()` functions directly.
71
+
72
+ To add a new command:
73
+
74
+ 1. Add a handler function in the appropriate module (or create a new one)
75
+ 2. Add argparse registration in that module's `register()` function
76
+ 3. The router picks it up automatically
77
+
78
+ ## Output Convention
79
+
80
+ Every command supports `--json` for structured output. The `format_output(args, text, json_data=...)` function in `util/formatting.py` handles routing -- it checks `args.json` and outputs either human-readable text or JSON accordingly.
81
+
82
+ ## Batch Operations & Undo
83
+
84
+ Batch commands (`batch-read`, `batch-move`, `batch-delete`, `batch-flag`) log their operations to `mail-undo.json`. The `undo` command reads this log to reverse the most recent batch operation. Up to 10 operations are retained.
85
+
86
+ ## Testing
87
+
88
+ Tests live in `tests/` and use `unittest.mock` to mock AppleScript calls. No actual Mail.app interaction happens during testing. Run with `pytest`.
89
+
90
+ The suite has 348 tests across 17 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.
@@ -0,0 +1,73 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.3.0] - 2026-02-24
10
+
11
+ ### Changed
12
+
13
+ - **Renamed project** — `my-apple-mail-cli` is now `mxctl` ("mail control")
14
+ - **Flattened CLI** — `my mail inbox` is now `mxctl inbox` (removed `mail` subcommand layer)
15
+ - **Config path** — moved from `~/.config/my/` to `~/.config/mxctl/` with automatic one-time migration
16
+ - **Version bump** — 0.2.0 → 0.3.0 (breaking: new binary name and command structure)
17
+ - **Package name** — Python package renamed from `my_cli` to `mxctl`
18
+ - **GitHub repo** — `Jscoats/my-apple-mail-cli` → `Jscoats/mxctl`
19
+ - Test suite expanded to 422 tests
20
+
21
+ ## [0.2.0] - 2026-02-22
22
+
23
+ ### Added
24
+
25
+ - `top-senders` command — rank senders by frequency with `--limit N`, `--json`, and optional mailbox filter
26
+ - `batch-delete --from-sender EMAIL` flag — delete by sender across all mailboxes, combinable with `--older-than`
27
+ - `NOREPLY_PATTERNS` centralized in `config.py` — single source of truth used by `triage`, `process-inbox`, `clean-newsletters`, and `weekly-review`
28
+ - `TEMPLATES_FILE` path centralized in `config.py` — imported by `templates.py` and `compose.py`
29
+ - File locking (`_file_lock`) applied to all JSON state reads and writes — `config.json`, `state.json`, `mail-undo.json`, `mail-templates.json`
30
+ - 30 new tests covering templates CRUD, draft error paths, and batch dry-run edge cases (196 total)
31
+
32
+ ### Fixed
33
+
34
+ - **Silent output failures** — three `cmd_mailboxes`, `_list_rules`, and `weekly-review` AppleScript strings were plain strings instead of f-strings; `FIELD_SEPARATOR` was passed as literal text causing commands to silently return empty results
35
+ - `batch-delete` indexed iteration bug — list shifts after each delete causing skips and crashes
36
+ - `batch-delete` Gmail All Mail error — individual deletions wrapped in `try/end try` so one IMAP failure doesn't abort the whole batch
37
+ - `batch-delete` message IDs now logged only after a successful delete (previously logged before, so failed deletions appeared in the undo log)
38
+ - `batch-move` — no error resilience; inner loop now wrapped in `try/end try` matching `batch-delete` pattern
39
+ - `batch-move` — `--limit` only exited inner loop, allowing more messages to be processed than requested; outer loop now also checks limit
40
+ - `batch-move` and `batch-delete` — dry-run reported total matching count instead of effective count when `--limit` is set
41
+ - `cmd_not_junk` — hardcoded `"Junk"` mailbox name broke on Gmail accounts; now uses `resolve_mailbox()` for `[Gmail]/Spam` translation
42
+ - `cmd_move` — source and destination mailbox names not translated for Gmail accounts; `resolve_mailbox()` now applied to both
43
+ - `triage`, `process-inbox`, `clean-newsletters`, `weekly-review` — noreply pattern matching ran against full sender string including display name; now uses `extract_email()` to match against email address only
44
+ - `compose.py` — template file read in `cmd_draft` had no JSON error handling; corrupt file now produces a friendly error
45
+ - `compose.py` — template file read now uses `_file_lock` (previously read outside locking protocol)
46
+ - `_file_lock` — file handle leaked on each failed retry attempt; fixed with `with open(...)` context manager
47
+ - `setup.py` `cmd_init` — config written with bare `open()` bypassing `_file_lock`; now uses `_save_json()`
48
+ - `_load_json` — reads were not locked (writes were); now symmetric
49
+ - `list` command now accepts `-m`/`--mailbox` flag (was positional-only, inconsistent with all other commands)
50
+ - `inbox` and `triage` now accept `-a` / `--account` to scope results to a single account
51
+ - Gmail mailbox name mapping — `init` now asks which accounts are Gmail; `Spam`, `Trash`, `Sent`, `Archive` etc. auto-translate to `[Gmail]/...` equivalents
52
+ - `accounts --json` returning empty `[]` instead of account data
53
+ - `die()` return type corrected to `NoReturn`
54
+ - Raw `\x1F`/`\x1FEND\x1F` literals in AppleScript strings replaced with `FIELD_SEPARATOR`/`RECORD_SEPARATOR` constants throughout
55
+ - Removed dead code: unused `_convert_dates`, `account_iterator`, `single_message_lookup` functions; unused variable assignments in `todoist_integration.py` and `system.py`
56
+
57
+ ## [0.1.0] - 2026-02-21
58
+
59
+ ### Added
60
+
61
+ - **First-run setup** — `init` wizard for account detection and optional Todoist token configuration
62
+ - **Account management** — `inbox`, `accounts`, `mailboxes`, `create-mailbox`, `delete-mailbox`, `empty-trash`, `count` for scripting and status bars
63
+ - **Message operations** — `list`, `read`, `search`, `mark-read`, `mark-unread`, `flag`, `unflag`, `move`, `delete`, `open` for GUI access
64
+ - **AI-powered features** — `summary`, `triage`, `context`, `find-related`, `process-inbox`
65
+ - **Batch operations with undo** — `batch-read`, `batch-flag`, `batch-move`, `batch-delete`, `undo`
66
+ - **Analytics** — `stats`, `digest`, `show-flagged`, `weekly-review`, `clean-newsletters`
67
+ - **Compose & templates** — `draft` with template support, `reply`, `forward`, `templates` subcommands
68
+ - **Integrations** — `to-todoist` for sending emails as Todoist tasks, `export` for Mbox format
69
+ - **System tools** — `check`, `headers`, `rules`, `junk`, `not-junk`
70
+ - Multi-account support with three-tier account resolution
71
+ - `--json` output mode on every command
72
+ - Comprehensive test suite (166 tests)
73
+ - Zero runtime dependencies (Python stdlib only)
@@ -0,0 +1,39 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, religion, or sexual identity
10
+ and orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to a positive environment:
15
+
16
+ - Using welcoming and inclusive language
17
+ - Being respectful of differing viewpoints and experiences
18
+ - Gracefully accepting constructive criticism
19
+ - Focusing on what is best for the community
20
+
21
+ Examples of unacceptable behavior:
22
+
23
+ - Trolling, insulting or derogatory comments, and personal or political attacks
24
+ - Public or private harassment
25
+ - Publishing others' private information without explicit permission
26
+ - Other conduct which could reasonably be considered inappropriate
27
+
28
+ ## Enforcement
29
+
30
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
31
+ reported by opening an issue or contacting the project maintainer. All
32
+ complaints will be reviewed and investigated and will result in a response
33
+ that is deemed necessary and appropriate to the circumstances.
34
+
35
+ ## Attribution
36
+
37
+ This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org),
38
+ version 2.1, available at
39
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).
@@ -0,0 +1,144 @@
1
+ # Contributing to mxctl
2
+
3
+ Thanks for your interest in contributing!
4
+
5
+ This project welcomes contributions from the community. Whether you're fixing a bug, adding a feature, or improving documentation, your help is appreciated.
6
+
7
+ ## Reporting Bugs
8
+
9
+ Found a bug? Please [open an issue](https://github.com/Jscoats/mxctl/issues/new?template=bug_report.md) with:
10
+
11
+ - Clear description of the problem
12
+ - Steps to reproduce
13
+ - Expected vs actual behavior
14
+ - Your environment (macOS version, Python version, Mail.app version)
15
+ - Relevant error messages or logs
16
+
17
+ **Note:** Please check existing issues first to avoid duplicates.
18
+
19
+ ## Requesting Features
20
+
21
+ Have an idea? [Open a feature request](https://github.com/Jscoats/mxctl/issues/new?template=feature_request.md) with:
22
+
23
+ - Clear description of the feature
24
+ - Why it would be useful
25
+ - Example usage (if applicable)
26
+
27
+ ## Contributing Code
28
+
29
+ ### Prerequisites
30
+
31
+ > **macOS required.** This tool uses AppleScript to control Mail.app, which is macOS-only. Contributors on Linux or Windows cannot run or test the tool. All development and testing must be done on macOS.
32
+
33
+ - macOS 12 or later
34
+ - Python 3.10+
35
+ - Mail.app with at least one configured account
36
+ - [`uv`](https://docs.astral.sh/uv/) (recommended) or `pip`
37
+
38
+ ### Before You Start
39
+
40
+ 1. **Check existing issues** - Someone might already be working on it
41
+ 2. **Discuss major changes** - Open an issue first for big features/refactors
42
+ 3. **Keep it focused** - One feature/fix per pull request
43
+
44
+ ### Development Setup
45
+
46
+ ```bash
47
+ # Clone the repo
48
+ git clone https://github.com/Jscoats/mxctl.git
49
+ cd mxctl
50
+
51
+ # Install in editable mode (primary -- uses uv)
52
+ uv tool install -e . # Install
53
+ uv tool install -e . --force # Reinstall after changes
54
+
55
+ # Run tests
56
+ pytest
57
+ ```
58
+
59
+ **Fallback:** If you are not using `uv`, you can install with pip instead:
60
+ ```bash
61
+ pip install -e ".[dev]"
62
+ ```
63
+
64
+ ### Code Guidelines
65
+
66
+ **Follow existing patterns:**
67
+ - Match the style of surrounding code
68
+ - Use existing utilities in `src/mxctl/util/`
69
+ - Add AppleScript code to `applescript_templates.py` if reusable
70
+ - Keep functions focused and testable
71
+
72
+ **Zero runtime dependencies:**
73
+ - This project uses only Python stdlib
74
+ - Do not add external packages without discussion
75
+
76
+ **Testing:**
77
+ - Add tests for new features
78
+ - Ensure existing tests pass: `pytest`
79
+ - Test files live in `tests/`
80
+ - Mock AppleScript calls in tests (see existing test files for examples)
81
+
82
+ **Documentation:**
83
+ - Update README.md if adding user-facing features
84
+ - Add docstrings to new functions
85
+ - Update ARCHITECTURE.md for architectural changes
86
+
87
+ ### Pull Request Process
88
+
89
+ 1. **Fork the repo** and create a feature branch
90
+ ```bash
91
+ git checkout -b feature/my-new-feature
92
+ ```
93
+
94
+ 2. **Make your changes**
95
+ - Write clean, readable code
96
+ - Add tests for new functionality
97
+ - Update documentation
98
+
99
+ 3. **Test thoroughly**
100
+ ```bash
101
+ pytest # All tests must pass
102
+ ```
103
+
104
+ 4. **Commit with clear messages**
105
+ ```bash
106
+ git commit -m "Add email filter by date range"
107
+ ```
108
+
109
+ 5. **Push and create a pull request**
110
+ ```bash
111
+ git push origin feature/my-new-feature
112
+ ```
113
+
114
+ 6. **Describe your changes** in the PR:
115
+ - What does it do?
116
+ - Why is it needed?
117
+ - How was it tested?
118
+
119
+ ### Code Review
120
+
121
+ - PRs will be reviewed for code quality, test coverage, and alignment with project goals
122
+ - Be patient - reviews may take a few days
123
+ - Be open to feedback and iteration
124
+
125
+ ## Documentation
126
+
127
+ Documentation improvements are always welcome! You can help by:
128
+
129
+ - Fixing typos or unclear wording
130
+ - Adding examples to the README
131
+ - Improving code comments
132
+ - Writing tutorials or guides
133
+
134
+ ## Questions?
135
+
136
+ Not sure about something? Open an issue with the `question` label or reach out via GitHub discussions.
137
+
138
+ ## Thank You!
139
+
140
+ Every contribution, no matter how small, helps make this project better. Thank you for being part of the community!
141
+
142
+ ---
143
+
144
+ **By contributing, you agree that your contributions will be licensed under the project's MIT License.**
mxctl-0.3.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jscoats
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.