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.
- mxctl-0.3.0/.github/ISSUE_TEMPLATE/bug_report.md +35 -0
- mxctl-0.3.0/.github/ISSUE_TEMPLATE/feature_request.md +25 -0
- mxctl-0.3.0/.github/PULL_REQUEST_TEMPLATE.md +19 -0
- mxctl-0.3.0/.github/dependabot.yml +10 -0
- mxctl-0.3.0/.github/workflows/ci.yml +31 -0
- mxctl-0.3.0/.github/workflows/release.yml +36 -0
- mxctl-0.3.0/.gitignore +42 -0
- mxctl-0.3.0/.pre-commit-config.yaml +7 -0
- mxctl-0.3.0/ARCHITECTURE.md +90 -0
- mxctl-0.3.0/CHANGELOG.md +73 -0
- mxctl-0.3.0/CODE_OF_CONDUCT.md +39 -0
- mxctl-0.3.0/CONTRIBUTING.md +144 -0
- mxctl-0.3.0/LICENSE +21 -0
- mxctl-0.3.0/PKG-INFO +439 -0
- mxctl-0.3.0/README.md +407 -0
- mxctl-0.3.0/SECURITY.md +32 -0
- mxctl-0.3.0/demo/ai-demo.gif +0 -0
- mxctl-0.3.0/demo/batch-delete-demo.gif +0 -0
- mxctl-0.3.0/demo/demo.gif +0 -0
- mxctl-0.3.0/demo/demo.tape +41 -0
- mxctl-0.3.0/demo/init-demo.gif +0 -0
- mxctl-0.3.0/demo/unsubscribe-demo.gif +0 -0
- mxctl-0.3.0/pyproject.toml +62 -0
- mxctl-0.3.0/src/mxctl/__init__.py +3 -0
- mxctl-0.3.0/src/mxctl/__main__.py +5 -0
- mxctl-0.3.0/src/mxctl/commands/__init__.py +0 -0
- mxctl-0.3.0/src/mxctl/commands/mail/__init__.py +1 -0
- mxctl-0.3.0/src/mxctl/commands/mail/accounts.py +347 -0
- mxctl-0.3.0/src/mxctl/commands/mail/actions.py +593 -0
- mxctl-0.3.0/src/mxctl/commands/mail/ai.py +355 -0
- mxctl-0.3.0/src/mxctl/commands/mail/analytics.py +390 -0
- mxctl-0.3.0/src/mxctl/commands/mail/attachments.py +156 -0
- mxctl-0.3.0/src/mxctl/commands/mail/batch.py +402 -0
- mxctl-0.3.0/src/mxctl/commands/mail/compose.py +133 -0
- mxctl-0.3.0/src/mxctl/commands/mail/composite.py +430 -0
- mxctl-0.3.0/src/mxctl/commands/mail/inbox_tools.py +502 -0
- mxctl-0.3.0/src/mxctl/commands/mail/manage.py +185 -0
- mxctl-0.3.0/src/mxctl/commands/mail/messages.py +363 -0
- mxctl-0.3.0/src/mxctl/commands/mail/setup.py +362 -0
- mxctl-0.3.0/src/mxctl/commands/mail/system.py +202 -0
- mxctl-0.3.0/src/mxctl/commands/mail/templates.py +145 -0
- mxctl-0.3.0/src/mxctl/commands/mail/todoist_integration.py +145 -0
- mxctl-0.3.0/src/mxctl/commands/mail/undo.py +323 -0
- mxctl-0.3.0/src/mxctl/config.py +227 -0
- mxctl-0.3.0/src/mxctl/main.py +89 -0
- mxctl-0.3.0/src/mxctl/util/__init__.py +0 -0
- mxctl-0.3.0/src/mxctl/util/applescript.py +139 -0
- mxctl-0.3.0/src/mxctl/util/applescript_templates.py +185 -0
- mxctl-0.3.0/src/mxctl/util/dates.py +54 -0
- mxctl-0.3.0/src/mxctl/util/formatting.py +52 -0
- mxctl-0.3.0/src/mxctl/util/mail_helpers.py +192 -0
- mxctl-0.3.0/tests/__init__.py +0 -0
- mxctl-0.3.0/tests/conftest.py +43 -0
- mxctl-0.3.0/tests/test_ai_classification.py +413 -0
- mxctl-0.3.0/tests/test_applescript.py +126 -0
- mxctl-0.3.0/tests/test_commands.py +1607 -0
- mxctl-0.3.0/tests/test_compose_batch_errors.py +552 -0
- mxctl-0.3.0/tests/test_config.py +93 -0
- mxctl-0.3.0/tests/test_count.py +102 -0
- mxctl-0.3.0/tests/test_dates.py +94 -0
- mxctl-0.3.0/tests/test_error_paths.py +488 -0
- mxctl-0.3.0/tests/test_formatting.py +111 -0
- mxctl-0.3.0/tests/test_mail_helpers.py +82 -0
- mxctl-0.3.0/tests/test_manage.py +280 -0
- mxctl-0.3.0/tests/test_new_coverage.py +1169 -0
- mxctl-0.3.0/tests/test_resolve_context.py +86 -0
- mxctl-0.3.0/tests/test_setup.py +275 -0
- mxctl-0.3.0/tests/test_stats_undo.py +461 -0
- mxctl-0.3.0/tests/test_templates.py +188 -0
- 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,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,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.
|
mxctl-0.3.0/CHANGELOG.md
ADDED
|
@@ -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.
|