muttlike-imap 1.0.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.
- muttlike_imap-1.0.0/.github/ISSUE_TEMPLATE/bug_report.md +37 -0
- muttlike_imap-1.0.0/.github/ISSUE_TEMPLATE/config.yml +8 -0
- muttlike_imap-1.0.0/.github/ISSUE_TEMPLATE/feature_request.md +30 -0
- muttlike_imap-1.0.0/.github/dependabot.yml +11 -0
- muttlike_imap-1.0.0/.github/pull_request_template.md +29 -0
- muttlike_imap-1.0.0/.github/workflows/ci.yml +36 -0
- muttlike_imap-1.0.0/.github/workflows/release.yml +47 -0
- muttlike_imap-1.0.0/.gitignore +17 -0
- muttlike_imap-1.0.0/CHANGELOG.md +40 -0
- muttlike_imap-1.0.0/CODE_OF_CONDUCT.md +30 -0
- muttlike_imap-1.0.0/CONTRIBUTING.md +56 -0
- muttlike_imap-1.0.0/LICENSE +21 -0
- muttlike_imap-1.0.0/PKG-INFO +234 -0
- muttlike_imap-1.0.0/README.md +203 -0
- muttlike_imap-1.0.0/SECURITY.md +5 -0
- muttlike_imap-1.0.0/docs/openclaw.md +76 -0
- muttlike_imap-1.0.0/docs/pattern-syntax.md +156 -0
- muttlike_imap-1.0.0/docs/secrets.md +121 -0
- muttlike_imap-1.0.0/pyproject.toml +90 -0
- muttlike_imap-1.0.0/src/muttlike_imap/__init__.py +3 -0
- muttlike_imap-1.0.0/src/muttlike_imap/__main__.py +5 -0
- muttlike_imap-1.0.0/src/muttlike_imap/cli.py +110 -0
- muttlike_imap-1.0.0/src/muttlike_imap/client.py +176 -0
- muttlike_imap-1.0.0/src/muttlike_imap/config.py +163 -0
- muttlike_imap-1.0.0/src/muttlike_imap/dates.py +238 -0
- muttlike_imap-1.0.0/src/muttlike_imap/mailbox.py +89 -0
- muttlike_imap-1.0.0/src/muttlike_imap/output.py +27 -0
- muttlike_imap-1.0.0/src/muttlike_imap/parser.py +350 -0
- muttlike_imap-1.0.0/src/muttlike_imap/sizes.py +57 -0
- muttlike_imap-1.0.0/tests/__init__.py +0 -0
- muttlike_imap-1.0.0/tests/test_cli.py +140 -0
- muttlike_imap-1.0.0/tests/test_client.py +397 -0
- muttlike_imap-1.0.0/tests/test_config.py +232 -0
- muttlike_imap-1.0.0/tests/test_dates.py +251 -0
- muttlike_imap-1.0.0/tests/test_mailbox.py +70 -0
- muttlike_imap-1.0.0/tests/test_output.py +58 -0
- muttlike_imap-1.0.0/tests/test_parser.py +318 -0
- muttlike_imap-1.0.0/tests/test_sizes.py +60 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Bug report
|
|
3
|
+
about: Report a bug or unexpected behaviour in muttlike-imap
|
|
4
|
+
title: ''
|
|
5
|
+
labels: bug
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Environment
|
|
9
|
+
|
|
10
|
+
- **muttlike-imap version**: <!-- output of `muttlike-imap --version` -->
|
|
11
|
+
- **Python version**: <!-- output of `python3 --version` -->
|
|
12
|
+
- **Operating system**: <!-- e.g. Ubuntu 24.04, macOS 14, Debian 12 -->
|
|
13
|
+
- **IMAP server**: <!-- e.g. Dovecot, Cyrus, Gmail, Fastmail, etc. -->
|
|
14
|
+
|
|
15
|
+
## Describe the bug
|
|
16
|
+
|
|
17
|
+
<!-- A clear and concise description of what you expected and what actually happened. -->
|
|
18
|
+
|
|
19
|
+
## Reproducer
|
|
20
|
+
|
|
21
|
+
<!--
|
|
22
|
+
The exact command line you ran. Redact mailbox or address details if
|
|
23
|
+
you need to, but please keep the pattern argument intact -- it's the
|
|
24
|
+
part most likely to be at fault.
|
|
25
|
+
-->
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
muttlike-imap "..." --mailbox INBOX --summary
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Expected output
|
|
32
|
+
|
|
33
|
+
<!-- What you expected the command to return or do. -->
|
|
34
|
+
|
|
35
|
+
## Actual output
|
|
36
|
+
|
|
37
|
+
<!-- The error message or wrong result you observed. Paste it in full. -->
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
blank_issues_enabled: true
|
|
2
|
+
contact_links:
|
|
3
|
+
- name: Pattern syntax reference
|
|
4
|
+
url: https://github.com/PierreSenellart/muttlike-imap/blob/main/docs/pattern-syntax.md
|
|
5
|
+
about: Full reference for the mutt-style pattern grammar.
|
|
6
|
+
- name: Storing the IMAP password securely
|
|
7
|
+
url: https://github.com/PierreSenellart/muttlike-imap/blob/main/docs/secrets.md
|
|
8
|
+
about: Worked examples with pass, gpg, and OS keyrings.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Feature request
|
|
3
|
+
about: Suggest a new modifier, CLI flag, or behaviour improvement
|
|
4
|
+
title: ''
|
|
5
|
+
labels: enhancement
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Use case
|
|
9
|
+
|
|
10
|
+
<!--
|
|
11
|
+
What problem are you trying to solve? A concrete scenario
|
|
12
|
+
("I want to find X but currently have to ...") helps more than an
|
|
13
|
+
abstract request.
|
|
14
|
+
-->
|
|
15
|
+
|
|
16
|
+
## Proposed solution
|
|
17
|
+
|
|
18
|
+
<!--
|
|
19
|
+
What would the feature look like from a user's perspective? If you
|
|
20
|
+
have a concrete CLI shape or pattern syntax in mind, show an example
|
|
21
|
+
invocation.
|
|
22
|
+
-->
|
|
23
|
+
|
|
24
|
+
## Alternatives considered
|
|
25
|
+
|
|
26
|
+
<!-- Other ways to solve the same problem; why they're less suitable. -->
|
|
27
|
+
|
|
28
|
+
## Background / references
|
|
29
|
+
|
|
30
|
+
<!-- Pointers to mutt's documentation, RFCs, or related tools that are relevant. Optional. -->
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# To get started with Dependabot version updates, you'll need to specify which
|
|
2
|
+
# package ecosystems to update and where the package manifests are located.
|
|
3
|
+
# Please see the documentation for all configuration options:
|
|
4
|
+
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
|
5
|
+
|
|
6
|
+
version: 2
|
|
7
|
+
updates:
|
|
8
|
+
- package-ecosystem: "github-actions" # See documentation for possible values
|
|
9
|
+
directory: "/" # Location of package manifests
|
|
10
|
+
schedule:
|
|
11
|
+
interval: "weekly"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<!-- Thanks for contributing to muttlike-imap! -->
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
<!-- What does this PR change? One or two sentences. -->
|
|
6
|
+
|
|
7
|
+
## Motivation
|
|
8
|
+
|
|
9
|
+
<!--
|
|
10
|
+
Why is this change necessary? Link to the related issue if applicable:
|
|
11
|
+
|
|
12
|
+
Closes #123
|
|
13
|
+
Fixes #456
|
|
14
|
+
-->
|
|
15
|
+
|
|
16
|
+
## Checklist
|
|
17
|
+
|
|
18
|
+
- [ ] `pytest` passes locally (no IMAP server needed for the default suite)
|
|
19
|
+
- [ ] `ruff check .` and `ruff format --check .` pass
|
|
20
|
+
- [ ] Tests added or updated for any code change
|
|
21
|
+
- [ ] `CHANGELOG.md` entry added under `[Unreleased]`
|
|
22
|
+
- [ ] User-visible behaviour reflected in `README.md`, `docs/pattern-syntax.md`, or `docs/secrets.md` as appropriate
|
|
23
|
+
|
|
24
|
+
## Notes for reviewers
|
|
25
|
+
|
|
26
|
+
<!--
|
|
27
|
+
Anything reviewers should pay extra attention to? Sharp edges,
|
|
28
|
+
performance trade-offs, IMAP-protocol surprises, etc.
|
|
29
|
+
-->
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
lint:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v6
|
|
17
|
+
- uses: actions/setup-python@v6
|
|
18
|
+
with:
|
|
19
|
+
python-version: "3.12"
|
|
20
|
+
- run: pip install ruff
|
|
21
|
+
- run: ruff check .
|
|
22
|
+
- run: ruff format --check .
|
|
23
|
+
|
|
24
|
+
test:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
strategy:
|
|
27
|
+
fail-fast: false
|
|
28
|
+
matrix:
|
|
29
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v6
|
|
32
|
+
- uses: actions/setup-python@v6
|
|
33
|
+
with:
|
|
34
|
+
python-version: ${{ matrix.python-version }}
|
|
35
|
+
- run: pip install -e ".[dev]"
|
|
36
|
+
- run: pytest --cov --cov-report=term-missing
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
name: Build sdist and wheel
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v6
|
|
18
|
+
- uses: actions/setup-python@v6
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
- name: Install build
|
|
22
|
+
run: python -m pip install --upgrade build
|
|
23
|
+
- name: Build distribution
|
|
24
|
+
run: python -m build
|
|
25
|
+
- name: Upload distribution artifacts
|
|
26
|
+
uses: actions/upload-artifact@v4
|
|
27
|
+
with:
|
|
28
|
+
name: distribution
|
|
29
|
+
path: dist/
|
|
30
|
+
|
|
31
|
+
publish:
|
|
32
|
+
name: Publish to PyPI
|
|
33
|
+
needs: build
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
environment:
|
|
36
|
+
name: pypi
|
|
37
|
+
url: https://pypi.org/p/muttlike-imap
|
|
38
|
+
permissions:
|
|
39
|
+
id-token: write
|
|
40
|
+
steps:
|
|
41
|
+
- name: Download distribution artifacts
|
|
42
|
+
uses: actions/download-artifact@v4
|
|
43
|
+
with:
|
|
44
|
+
name: distribution
|
|
45
|
+
path: dist/
|
|
46
|
+
- name: Publish to PyPI
|
|
47
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes are documented here. The format is loosely based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
5
|
+
adheres to [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [1.0.0]: Initial release
|
|
10
|
+
|
|
11
|
+
First public release.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- Mutt-compatible pattern parser supporting AND (juxtaposition), `|` OR,
|
|
15
|
+
`!` NOT, and `(...)` grouping.
|
|
16
|
+
- Text modifiers: `~f`, `~t`, `~s`, `~b`, `~B`, `~c`, `~C`, `~L`, `~e`, `~i`,
|
|
17
|
+
`~y`, `~h`, `~x`.
|
|
18
|
+
- Flag modifiers: `~A`, `~U`, `~N`, `~R`, `~O`, `~F`, `~D`, `~Q`, `~p`, `~P`.
|
|
19
|
+
- Date modifiers `~d` and `~r` with the full mutt DATERANGE grammar:
|
|
20
|
+
relative (`<Nu`, `>Nu`, `=Nu`), absolute (`D/M/Y` and ISO `YYYY-MM-DD`),
|
|
21
|
+
ranges, half-open ranges, and error margins (`*Nu`). Sub-day units
|
|
22
|
+
(`H`/`M`/`S`) get day-rounded server-side filtering plus a Python
|
|
23
|
+
post-filter against `Date:` (or `INTERNALDATE` for `~r`), recovering
|
|
24
|
+
precise sub-day windows like `~d <30M` for "last 30 minutes".
|
|
25
|
+
- Size modifier `~z` with `<`, `>`, range, and inclusive/exclusive forms.
|
|
26
|
+
- `--list-mailboxes` for folder discovery.
|
|
27
|
+
- Modified UTF-7 (RFC 3501 §5.1.3) encoding/decoding for non-ASCII folder
|
|
28
|
+
names like `Éléments envoyés`.
|
|
29
|
+
- ASCII diacritic folding so `~f Müller` matches both UTF-8 and ASCII forms.
|
|
30
|
+
- Layered config: CLI flags > `IMAPQUERY_*` env vars > `$IMAPQUERY_CONFIG` >
|
|
31
|
+
`~/.config/muttlike-imap/config` > legacy `~/.config/imap-smtp-email/.env`.
|
|
32
|
+
- `--imap-password-cmd` flag and `IMAP_PASS_CMD` config key for fetching the
|
|
33
|
+
password from `pass`, `gpg`, `secret-tool`, the macOS Keychain, etc.,
|
|
34
|
+
without storing it on disk or exporting it through the environment.
|
|
35
|
+
- `--imap-password-env` flag for picking up an already-set environment
|
|
36
|
+
variable.
|
|
37
|
+
- Library API: `parse_pattern`, `search`, `list_mailboxes`, `load_config`.
|
|
38
|
+
|
|
39
|
+
[Unreleased]: https://github.com/PierreSenellart/muttlike-imap/compare/v1.0.0...HEAD
|
|
40
|
+
[1.0.0]: https://github.com/PierreSenellart/muttlike-imap/releases/tag/v1.0.0
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
`muttlike-imap` is a small open-source utility. Most interaction happens
|
|
4
|
+
through GitHub issues and pull requests; this code of conduct sets the
|
|
5
|
+
basic expectations for those exchanges.
|
|
6
|
+
|
|
7
|
+
## Expected behavior
|
|
8
|
+
|
|
9
|
+
- Be respectful and constructive in all interactions: issues, pull
|
|
10
|
+
requests, code reviews, and any other discussion.
|
|
11
|
+
- Welcome newcomers, especially first-time contributors.
|
|
12
|
+
- Accept constructive criticism gracefully; offer it the same way.
|
|
13
|
+
|
|
14
|
+
## Unacceptable behavior
|
|
15
|
+
|
|
16
|
+
- Harassment, intimidation, or discrimination of any kind.
|
|
17
|
+
- Personal attacks or derogatory comments.
|
|
18
|
+
- Publishing others' private information without consent.
|
|
19
|
+
|
|
20
|
+
## Scope
|
|
21
|
+
|
|
22
|
+
This code of conduct applies to all project spaces: the GitHub
|
|
23
|
+
repository, issue tracker, pull requests, and any other communication
|
|
24
|
+
channel associated with the project.
|
|
25
|
+
|
|
26
|
+
## Enforcement
|
|
27
|
+
|
|
28
|
+
Instances of unacceptable behavior may be reported to the project
|
|
29
|
+
maintainer, [Pierre Senellart](mailto:pierre@senellart.com). Reports
|
|
30
|
+
will be handled confidentially.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Contributing to muttlike-imap
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing to muttlike-imap!
|
|
4
|
+
|
|
5
|
+
## Reporting bugs and requesting features
|
|
6
|
+
|
|
7
|
+
Please use the [GitHub issue
|
|
8
|
+
tracker](https://github.com/PierreSenellart/muttlike-imap/issues). Two
|
|
9
|
+
issue templates are provided and will auto-fill when you open a new
|
|
10
|
+
issue: a bug-report form (asking for `muttlike-imap --version`, your
|
|
11
|
+
Python version, OS, and a reproducer) and a feature-request form. For
|
|
12
|
+
security vulnerabilities, please use the [private security
|
|
13
|
+
advisory](https://github.com/PierreSenellart/muttlike-imap/security/advisories/new)
|
|
14
|
+
flow instead of a public issue (see [SECURITY.md](SECURITY.md)).
|
|
15
|
+
|
|
16
|
+
## Development setup
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
git clone https://github.com/PierreSenellart/muttlike-imap
|
|
20
|
+
cd muttlike-imap
|
|
21
|
+
pip install -e ".[dev]"
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
That installs `ruff` (lint + format) and `pytest` (with `pytest-cov`)
|
|
25
|
+
into your current Python. The package itself has no runtime
|
|
26
|
+
dependencies.
|
|
27
|
+
|
|
28
|
+
## Running the tests and linters
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
pytest # full suite, ~240 tests, no IMAP server needed
|
|
32
|
+
pytest --cov # with coverage
|
|
33
|
+
ruff check . # lint
|
|
34
|
+
ruff format --check . # check formatting
|
|
35
|
+
ruff format . # apply formatting
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
CI runs the same commands across Python 3.9 through 3.13.
|
|
39
|
+
|
|
40
|
+
## Submitting a pull request
|
|
41
|
+
|
|
42
|
+
1. Fork the repository and create a branch from `main`.
|
|
43
|
+
2. Make your changes. Add or update tests for any code change: the bar
|
|
44
|
+
is "the test would have caught this bug" or "the test demonstrates
|
|
45
|
+
the new behaviour".
|
|
46
|
+
3. Ensure `pytest`, `ruff check .`, and `ruff format --check .` pass.
|
|
47
|
+
4. Update `CHANGELOG.md` under `[Unreleased]` with a one-line summary,
|
|
48
|
+
and update `README.md` / `docs/pattern-syntax.md` / `docs/secrets.md`
|
|
49
|
+
if you change user-visible behaviour.
|
|
50
|
+
5. Open a pull request against `main` with a clear description of what
|
|
51
|
+
the change does and why.
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
By contributing, you agree that your contributions will be licensed
|
|
56
|
+
under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pierre Senellart
|
|
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.
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: muttlike-imap
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Search IMAP mailboxes from the command line using mutt-style patterns.
|
|
5
|
+
Project-URL: Homepage, https://github.com/PierreSenellart/muttlike-imap
|
|
6
|
+
Project-URL: Issues, https://github.com/PierreSenellart/muttlike-imap/issues
|
|
7
|
+
Project-URL: Changelog, https://github.com/PierreSenellart/muttlike-imap/blob/main/CHANGELOG.md
|
|
8
|
+
Author-email: Pierre Senellart <pierre@senellart.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: cli,email,imap,mutt,search
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Communications :: Email
|
|
24
|
+
Classifier: Topic :: Utilities
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=7; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# muttlike-imap
|
|
33
|
+
|
|
34
|
+
[](https://github.com/PierreSenellart/muttlike-imap/actions/workflows/ci.yml)
|
|
35
|
+
[](https://pypi.org/project/muttlike-imap/)
|
|
36
|
+
[](LICENSE)
|
|
37
|
+
|
|
38
|
+
Search IMAP mailboxes from the command line using mutt-style patterns.
|
|
39
|
+
|
|
40
|
+
```console
|
|
41
|
+
$ muttlike-imap "~f alice ~U" --summary
|
|
42
|
+
1 result(s):
|
|
43
|
+
|
|
44
|
+
UID:1264 | From:Alice <alice@example.com> | Date:Tue, 21 Apr 2026 15:02:35 +0000
|
|
45
|
+
Subject:Re: project update
|
|
46
|
+
Preview:Hi, here is the latest version of the document…
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Why
|
|
50
|
+
|
|
51
|
+
Mutt has a great pattern language for finding messages (`~f`, `~s`, `~d <7d`,
|
|
52
|
+
`!~U`, `(A | B) C`, …). IMAP servers can do most of the same searches, but the
|
|
53
|
+
wire protocol is verbose and the bindings in `imaplib` are awkward to compose.
|
|
54
|
+
`muttlike-imap` is a small CLI that translates mutt patterns into IMAP `SEARCH`
|
|
55
|
+
criteria and prints the matches as JSON or a human-readable summary.
|
|
56
|
+
|
|
57
|
+
It's intentionally small and scriptable – useful as a building block for
|
|
58
|
+
notification scripts, AI assistants, cron jobs, or one-off lookups.
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
pip install muttlike-imap
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Requires Python 3.9+. No third-party dependencies.
|
|
67
|
+
|
|
68
|
+
## Configure
|
|
69
|
+
|
|
70
|
+
`muttlike-imap` looks for connection settings in this order (highest priority
|
|
71
|
+
first):
|
|
72
|
+
|
|
73
|
+
1. CLI flags: `--imap-host`, `--imap-port`, `--imap-user`,
|
|
74
|
+
`--imap-password-cmd`, `--imap-password-env`, `--imap-tls`.
|
|
75
|
+
2. Environment variables: `IMAPQUERY_HOST`, `IMAPQUERY_PORT`, `IMAPQUERY_USER`,
|
|
76
|
+
`IMAPQUERY_PASS`, `IMAPQUERY_TLS`.
|
|
77
|
+
3. The file pointed to by `$IMAPQUERY_CONFIG`, if set.
|
|
78
|
+
4. `$XDG_CONFIG_HOME/muttlike-imap/config` (defaults to
|
|
79
|
+
`~/.config/muttlike-imap/config`).
|
|
80
|
+
5. `~/.config/imap-smtp-email/.env`: kept as a fallback for users who already
|
|
81
|
+
have this file from the
|
|
82
|
+
[openclaw imap-smtp-email skill](https://github.com/openclaw/imap-smtp-email).
|
|
83
|
+
See [`docs/openclaw.md`](docs/openclaw.md) for wiring `muttlike-imap`
|
|
84
|
+
into an openclaw workspace.
|
|
85
|
+
|
|
86
|
+
A minimal config file:
|
|
87
|
+
|
|
88
|
+
```ini
|
|
89
|
+
IMAP_HOST=imap.example.com
|
|
90
|
+
IMAP_PORT=993
|
|
91
|
+
IMAP_USER=you@example.com
|
|
92
|
+
IMAP_PASS_CMD=pass email/imap.example.com
|
|
93
|
+
IMAP_TLS=true
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The keys can be written with the `IMAP_` prefix (shown above, matches mutt and
|
|
97
|
+
offlineimap conventions), with the `IMAPQUERY_` prefix, or with no prefix at
|
|
98
|
+
all (`HOST=…`); pick whichever you prefer.
|
|
99
|
+
|
|
100
|
+
### Passwords
|
|
101
|
+
|
|
102
|
+
There are three ways to give `muttlike-imap` your password, in increasing
|
|
103
|
+
order of how much they leak:
|
|
104
|
+
|
|
105
|
+
- **`IMAP_PASS_CMD=<shell command>`** (recommended). The command is run on
|
|
106
|
+
every invocation; the first line of stdout is used as the password. Pair
|
|
107
|
+
with [`pass`](https://www.passwordstore.org/),
|
|
108
|
+
[`secret-tool`](https://wiki.gnome.org/Projects/Libsecret),
|
|
109
|
+
macOS Keychain (`security find-generic-password -w`), or anything else
|
|
110
|
+
that prints a secret to stdout. The password lives only in the
|
|
111
|
+
`muttlike-imap` process memory and is never written to disk or to the
|
|
112
|
+
environment.
|
|
113
|
+
|
|
114
|
+
Equivalent CLI flag: `--imap-password-cmd "pass email/imap.example.com"`.
|
|
115
|
+
|
|
116
|
+
- **`--imap-password-env MY_VAR`**. Reads the password from a named
|
|
117
|
+
environment variable. Useful when you already have a secret in a variable
|
|
118
|
+
(e.g. via `direnv` or a parent process) and don't want to re-export it.
|
|
119
|
+
Caveat: env vars leak through `/proc/<pid>/environ`, child processes,
|
|
120
|
+
`ps eww`, and crash dumps.
|
|
121
|
+
|
|
122
|
+
- **`IMAP_PASS=<plaintext>`** in the config file. Simplest, least secure;
|
|
123
|
+
fine for throwaway accounts and local-only setups. Make sure the file is
|
|
124
|
+
`chmod 600`.
|
|
125
|
+
|
|
126
|
+
See [`docs/secrets.md`](docs/secrets.md) for worked examples with `pass`,
|
|
127
|
+
raw `gpg`, and OS keyrings.
|
|
128
|
+
|
|
129
|
+
## Examples
|
|
130
|
+
|
|
131
|
+
By default the search runs against `INBOX` and returns the 10 most recent
|
|
132
|
+
matches as JSON. Pass `--mailbox <name>` to search elsewhere, `--limit N`
|
|
133
|
+
to widen or narrow the result count, and `--summary` for human-readable
|
|
134
|
+
output.
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
# Unread mail, default INBOX, summary view
|
|
138
|
+
muttlike-imap "~U" --summary
|
|
139
|
+
|
|
140
|
+
# All mail from Alice in the last week
|
|
141
|
+
muttlike-imap "~f alice ~d <7d" --summary
|
|
142
|
+
|
|
143
|
+
# Archived correspondence with Bob about a specific topic
|
|
144
|
+
muttlike-imap '~L bob ~s "project x"' --mailbox Archive --summary
|
|
145
|
+
|
|
146
|
+
# Anything addressed to me, last 30 days, that I haven't replied to
|
|
147
|
+
muttlike-imap '~p ~d <30d !~Q' --summary
|
|
148
|
+
|
|
149
|
+
# Date range, ISO format
|
|
150
|
+
muttlike-imap '~d 2025-09-01-2025-12-31 ~f committee' --summary
|
|
151
|
+
|
|
152
|
+
# JSON for piping into jq
|
|
153
|
+
muttlike-imap "~U" | jq '.[] | {uid, subject, from}'
|
|
154
|
+
|
|
155
|
+
# Discover available folders
|
|
156
|
+
muttlike-imap --list-mailboxes
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Pattern syntax
|
|
160
|
+
|
|
161
|
+
`A B` is AND (juxtaposition), `A | B` is OR, `!A` is NOT, and `(...)` groups.
|
|
162
|
+
|
|
163
|
+
| Modifier | Meaning |
|
|
164
|
+
|----------|---------|
|
|
165
|
+
| `~f <text>` | From contains |
|
|
166
|
+
| `~t <text>` | To contains |
|
|
167
|
+
| `~s <text>` | Subject contains |
|
|
168
|
+
| `~b <text>` | Body contains |
|
|
169
|
+
| `~B <text>` | Body or any header contains |
|
|
170
|
+
| `~c <text>` | Cc contains |
|
|
171
|
+
| `~C <text>` | To, Cc, or Bcc contains |
|
|
172
|
+
| `~L <text>` | Any participant (From/To/Cc) |
|
|
173
|
+
| `~e <text>` | Sender header |
|
|
174
|
+
| `~i <text>` | Message-ID |
|
|
175
|
+
| `~y <text>` | X-Label |
|
|
176
|
+
| `~h "Name: text"` | Arbitrary header |
|
|
177
|
+
| `~x <text>` | References / In-Reply-To |
|
|
178
|
+
| `~A` | All messages |
|
|
179
|
+
| `~U` / `~N` | Unread / new |
|
|
180
|
+
| `~R` / `~O` | Read / old |
|
|
181
|
+
| `~F` | Flagged |
|
|
182
|
+
| `~D` | Deleted |
|
|
183
|
+
| `~Q` | Replied (answered) |
|
|
184
|
+
| `~p` | Addressed to you |
|
|
185
|
+
| `~P` | From you |
|
|
186
|
+
| `~d <Nu` | Date: header newer than N units (units `y m w d H M S`) |
|
|
187
|
+
| `~d >Nu` | Date: header older than N units |
|
|
188
|
+
| `~d =Nu` | Exactly N units old |
|
|
189
|
+
| `~d DATE` | On a specific date (`YYYY-MM-DD` or `D/M/Y`) |
|
|
190
|
+
| `~d -DATE` / `~d DATE-` | Half-open range (before / since) |
|
|
191
|
+
| `~d DATE-DATE` | Range |
|
|
192
|
+
| `~d DATE*Nu` | Range of ±N units around DATE |
|
|
193
|
+
| `~r DATERANGE` | Same grammar but on received date (INTERNALDATE) |
|
|
194
|
+
| `~z <N` / `~z >N` / `~z N-M` | Size in bytes (suffix `K`/`M`) |
|
|
195
|
+
|
|
196
|
+
See [`docs/pattern-syntax.md`](docs/pattern-syntax.md) for the full reference.
|
|
197
|
+
|
|
198
|
+
### Limitations vs mutt
|
|
199
|
+
|
|
200
|
+
* **No regex.** IMAP `SEARCH` is substring-only, so all text matches are
|
|
201
|
+
literal. Anchors and character classes are not honored.
|
|
202
|
+
* **No mutt-runtime modifiers.** `~T` (tagged), `~v` (collapsed thread),
|
|
203
|
+
`~m` (message-number), `~n` (score), `~$`, `~#`, `~(...)` (thread patterns),
|
|
204
|
+
PGP modifiers: all error out instead of silently misbehaving.
|
|
205
|
+
|
|
206
|
+
`H`/`M`/`S` offsets are exact despite IMAP's day granularity: the server
|
|
207
|
+
narrows to the smallest whole-day window containing the precise range,
|
|
208
|
+
then a client-side post-filter trims the fetched candidates by their
|
|
209
|
+
`Date:` header (or `INTERNALDATE` for `~r`). `~d <30M` really does mean
|
|
210
|
+
"last 30 minutes". The post-filter is suppressed when the sub-day
|
|
211
|
+
modifier sits inside an `OR`, `!`, or paren-grouped disjunction, since
|
|
212
|
+
lifting its predicate to a top-level filter would change the pattern's
|
|
213
|
+
meaning.
|
|
214
|
+
|
|
215
|
+
## Library use
|
|
216
|
+
|
|
217
|
+
```python
|
|
218
|
+
from muttlike_imap.parser import parse_pattern
|
|
219
|
+
from muttlike_imap.client import search
|
|
220
|
+
from muttlike_imap.config import load_config
|
|
221
|
+
|
|
222
|
+
config = load_config() # or pass in your own dict
|
|
223
|
+
results = search(config, "~f alice ~U", limit=10, mailbox="INBOX")
|
|
224
|
+
for r in results:
|
|
225
|
+
print(r["subject"])
|
|
226
|
+
|
|
227
|
+
# Or just use the parser:
|
|
228
|
+
parse_pattern("(~f a | ~f b) ~U")
|
|
229
|
+
# → 'OR (FROM "a") (FROM "b") UNSEEN'
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
MIT: see [LICENSE](LICENSE).
|