diff options
Diffstat (limited to 'README.md')
| -rw-r--r-- | README.md | 111 |
1 files changed, 111 insertions, 0 deletions
diff --git a/README.md b/README.md new file mode 100644 index 0000000..3e1ef04 --- /dev/null +++ b/README.md @@ -0,0 +1,111 @@ +# mailctl + +A deliberately narrow, agent-safe CLI wrapper around [notmuch](https://notmuchmail.org/), meant for use by AI coding agents (Claude Code, opencode, etc.) to **search and organize** local mail. It has no ability to send: there is no SMTP, reply, or compose code path in the tool at all. + +## Why + +An agent given raw `notmuch`/`mbsync` access can do anything, including mangle tags across every account or trigger a sync at the wrong moment. `mailctl` closes that surface: + +- **Reads run freely**, mutations are gated. +- **Tag changes are dry-run by default**, and only touch the index with `--apply`. +- **Cross-account mutations require an explicit opt-in** (`--all-accounts`); the default forces a single `--account`. +- **Destructive changes** (adding `deleted`/`trash`/`spam`, removing `inbox`) need `--apply` **and** `--confirm-destructive`. +- **Every applied mutation is audited** to `~/.local/state/mailctl/audit.log`. +- **Drafting is possible, sending is not.** `mailctl draft` writes a local-only message into the account's Drafts folder for you to review and send yourself in neomutt. + +## Requirements + +- `notmuch` on `PATH`, with a synced Maildir at `~/Mail` (see Configuration). +- Python 3 (standard library only, no third-party packages). + +## Installation + +No installer, just copy files into place: + +```bash +cp mailctl.py mailsync.sh ~/bin/ # executables +cp -r mail-organize ~/.claude/skills/ # Claude Code skill, in its own dir +``` + +The skill invokes the tool at `~/bin/mailctl.py`; keep that path. + +## Configuration + +Your accounts are not in the source. `mailctl` reads them from `~/.config/mailctl/accounts.json` (override with the `MAILCTL_CONFIG` environment variable): + +```json +{ + "accounts": { + "work": { + "maildir": "work-mbsync-dir", + "address": "you@example.org", + "drafts": "Drafts" + }, + "personal": { + "maildir": "personal-mbsync-dir", + "address": "you@example.net", + "drafts": null + } + } +} +``` + +- `maildir` is the account's subdirectory under `~/Mail`, exactly as mbsync created it. +- `address` is the real From address, used only when writing a draft. +- `drafts` is the Drafts folder name inside that maildir, matching the account's mbsync `Patterns` (`Drafts`, `[Gmail]/Bozze`, ...). Use `null`, or omit it, if that account has no synced Drafts folder; `mailctl draft` then refuses that account. + +The account map stays a closed set. The config is checked against its schema **and** against the actual directories on disk when `mailctl` starts, and any problem is a hard exit before a query runs: a misspelled `maildir` can't silently produce a filter matching nothing, and a misspelled `drafts` can't put a draft somewhere mbsync never syncs. Set `MAILCTL_MAIL_ROOT` if your Maildir is not at `~/Mail`. + +## Usage + +```bash +mailctl search "<notmuch query>" [--account NAME] [--json] +mailctl show "<thread:id or id:msgid>" +mailctl tags # list all tags in use +mailctl count "<query>" [--account NAME] +mailctl senders "<query>" [--account NAME] [--top N] [--json] # ranked by count +mailctl subjects "<query>" [--account NAME] [--top N] [--json] # subject terms +mailctl tag "<query>" --account NAME [--add TAG]... [--remove TAG]... + [--apply] [--confirm-destructive] +mailctl draft --account NAME --to ADDR --subject TEXT + [--cc ADDR] [--body TEXT | --body-file PATH] +``` + +Reads default to global scope (all accounts) when `--account` is omitted. `tag` refuses to run without either `--account NAME` or `--all-accounts`. + +### Example + +```bash +# Preview: what would tagging these as 'newsletter' touch? +mailctl tag "from:substack.com" --account personal --add newsletter + +# Commit it after reviewing the preview +mailctl tag "from:substack.com" --account personal --add newsletter --apply +``` + +## Companion sync script + +`mailsync.sh` runs `mbsync -a` followed by `notmuch new`, with a `flock` guard and timestamped, rotated logging. It is meant for a cron/systemd timer and is **not** called by `mailctl.py`; sync and organization stay separate on purpose. + +## Claude Code skill + +`mail-organize/` is a Claude Code skill that makes `mailctl` the only sanctioned interface for an agent to touch your mail, and tightens the tool's gates into workflow rules (always dry-run first, never sync, never send, single-account scope). See `mail-organize/SKILL.md`. + +## License + +Copyright (C) 2026 Danilo M. <danix@danix.xyz> + +Released under the **GNU General Public License, version 2** (GPLv2-only). See +[`LICENSE`](LICENSE) for the full text. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. + +## Development Approach + +This project is developed using AI-assisted tools. Code is generated with the help of AI based on human-provided specifications, design decisions, and iterative feedback. + +All contributions are reviewed, tested, and curated by the maintainer before being included in the codebase. AI is used as a productivity and exploration tool, while human oversight remains central to all decisions. + +The goal is to combine the flexibility of AI-assisted development with standard open-source practices such as transparency, review, and accountability. |
