mail-notify 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.
- mail_notify-1.0.0/CHANGELOG.md +148 -0
- mail_notify-1.0.0/CODE_OF_CONDUCT.md +9 -0
- mail_notify-1.0.0/CONTRIBUTING.md +33 -0
- mail_notify-1.0.0/LICENSE +21 -0
- mail_notify-1.0.0/MANIFEST.in +4 -0
- mail_notify-1.0.0/PKG-INFO +388 -0
- mail_notify-1.0.0/README.md +358 -0
- mail_notify-1.0.0/SECURITY.md +46 -0
- mail_notify-1.0.0/docs/HOW-IT-WORKS.md +102 -0
- mail_notify-1.0.0/docs/RESOURCES.md +199 -0
- mail_notify-1.0.0/docs/STABILITY.md +33 -0
- mail_notify-1.0.0/docs/dev/ANALISI-APP-CLAUDE.md +125 -0
- mail_notify-1.0.0/docs/dev/PIANO-v0.3.md +34 -0
- mail_notify-1.0.0/pyproject.toml +47 -0
- mail_notify-1.0.0/setup.cfg +4 -0
- mail_notify-1.0.0/src/ccmail/__init__.py +1 -0
- mail_notify-1.0.0/src/ccmail/__main__.py +13 -0
- mail_notify-1.0.0/src/ccmail/ack.py +178 -0
- mail_notify-1.0.0/src/ccmail/activity.py +136 -0
- mail_notify-1.0.0/src/ccmail/auth.py +67 -0
- mail_notify-1.0.0/src/ccmail/autostart.py +142 -0
- mail_notify-1.0.0/src/ccmail/chats.py +238 -0
- mail_notify-1.0.0/src/ccmail/cli.py +536 -0
- mail_notify-1.0.0/src/ccmail/config.py +161 -0
- mail_notify-1.0.0/src/ccmail/credstore.py +274 -0
- mail_notify-1.0.0/src/ccmail/daemon.py +107 -0
- mail_notify-1.0.0/src/ccmail/db.py +245 -0
- mail_notify-1.0.0/src/ccmail/diagnostics.py +49 -0
- mail_notify-1.0.0/src/ccmail/heartbeat.py +105 -0
- mail_notify-1.0.0/src/ccmail/hookclient.py +112 -0
- mail_notify-1.0.0/src/ccmail/htmlmail.py +341 -0
- mail_notify-1.0.0/src/ccmail/i18n.py +80 -0
- mail_notify-1.0.0/src/ccmail/identity.py +35 -0
- mail_notify-1.0.0/src/ccmail/inbound.py +453 -0
- mail_notify-1.0.0/src/ccmail/installer.py +171 -0
- mail_notify-1.0.0/src/ccmail/launcher.py +73 -0
- mail_notify-1.0.0/src/ccmail/mailer.py +69 -0
- mail_notify-1.0.0/src/ccmail/paths.py +111 -0
- mail_notify-1.0.0/src/ccmail/policy.py +179 -0
- mail_notify-1.0.0/src/ccmail/projroot.py +84 -0
- mail_notify-1.0.0/src/ccmail/providers.py +52 -0
- mail_notify-1.0.0/src/ccmail/redact.py +20 -0
- mail_notify-1.0.0/src/ccmail/render.py +233 -0
- mail_notify-1.0.0/src/ccmail/server.py +450 -0
- mail_notify-1.0.0/src/ccmail/skill/SKILL.md +66 -0
- mail_notify-1.0.0/src/ccmail/skillinstall.py +58 -0
- mail_notify-1.0.0/src/ccmail/suggest.py +181 -0
- mail_notify-1.0.0/src/ccmail/summarize.py +181 -0
- mail_notify-1.0.0/src/ccmail/tracer.py +82 -0
- mail_notify-1.0.0/src/ccmail/wakeclient.py +63 -0
- mail_notify-1.0.0/src/ccmail/web/app.css +581 -0
- mail_notify-1.0.0/src/ccmail/web/app.js +1909 -0
- mail_notify-1.0.0/src/ccmail/web/favicon.svg +6 -0
- mail_notify-1.0.0/src/ccmail/web/index.html +471 -0
- mail_notify-1.0.0/src/ccmail/web/lang-en-app.js +436 -0
- mail_notify-1.0.0/src/ccmail/web/lang-en.js +194 -0
- mail_notify-1.0.0/src/ccmail/web/lang.js +127 -0
- mail_notify-1.0.0/src/ccmail/worker.py +82 -0
- mail_notify-1.0.0/src/mail_notify.egg-info/PKG-INFO +388 -0
- mail_notify-1.0.0/src/mail_notify.egg-info/SOURCES.txt +85 -0
- mail_notify-1.0.0/src/mail_notify.egg-info/dependency_links.txt +1 -0
- mail_notify-1.0.0/src/mail_notify.egg-info/entry_points.txt +3 -0
- mail_notify-1.0.0/src/mail_notify.egg-info/top_level.txt +1 -0
- mail_notify-1.0.0/tests/helpers.py +50 -0
- mail_notify-1.0.0/tests/test_ack.py +180 -0
- mail_notify-1.0.0/tests/test_api_limits.py +72 -0
- mail_notify-1.0.0/tests/test_chats.py +211 -0
- mail_notify-1.0.0/tests/test_cli_api.py +63 -0
- mail_notify-1.0.0/tests/test_discovery.py +130 -0
- mail_notify-1.0.0/tests/test_heartbeat.py +250 -0
- mail_notify-1.0.0/tests/test_htmlmail.py +259 -0
- mail_notify-1.0.0/tests/test_inbound.py +326 -0
- mail_notify-1.0.0/tests/test_inturn.py +99 -0
- mail_notify-1.0.0/tests/test_launcher.py +61 -0
- mail_notify-1.0.0/tests/test_mail_content.py +189 -0
- mail_notify-1.0.0/tests/test_matrix.py +147 -0
- mail_notify-1.0.0/tests/test_misc.py +155 -0
- mail_notify-1.0.0/tests/test_policy.py +171 -0
- mail_notify-1.0.0/tests/test_portable.py +254 -0
- mail_notify-1.0.0/tests/test_rejection.py +112 -0
- mail_notify-1.0.0/tests/test_reply_turn.py +74 -0
- mail_notify-1.0.0/tests/test_server.py +187 -0
- mail_notify-1.0.0/tests/test_setup.py +73 -0
- mail_notify-1.0.0/tests/test_suggest.py +54 -0
- mail_notify-1.0.0/tests/test_summarize.py +106 -0
- mail_notify-1.0.0/tests/test_webi18n.py +137 -0
- mail_notify-1.0.0/tests/test_wrapped_quote.py +21 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to ccmail. Versions follow [Semantic Versioning](https://semver.org/).
|
|
4
|
+
|
|
5
|
+
## 1.0.0 - 2026-10-13
|
|
6
|
+
|
|
7
|
+
First stable release. What is stable is listed in [docs/STABILITY.md](docs/STABILITY.md).
|
|
8
|
+
|
|
9
|
+
New: `mail-notify setup [--autostart]` installs the hooks, the skill and the short command in one step (`init`
|
|
10
|
+
stays separate: it asks for the password).
|
|
11
|
+
|
|
12
|
+
Fixed before release: `status` and `stop` no longer treat an answer from a foreign daemon (HTTP 401/403, for
|
|
13
|
+
example another data folder's) as "our daemon is running"; the source distribution now ships the full test suite
|
|
14
|
+
(`MANIFEST.in`).
|
|
15
|
+
|
|
16
|
+
The distribution (PyPI/pipx) name is `mail-notify`, matching the command and the repository; the Python module and
|
|
17
|
+
the daemon keep the name `ccmail`, and `ccmail` still works as a command.
|
|
18
|
+
|
|
19
|
+
### Known limitations
|
|
20
|
+
- macOS, Linux and mail providers other than Gmail (Outlook, iCloud and Yahoo presets) are covered by the
|
|
21
|
+
automated tests only; real-system testing is planned for a later release.
|
|
22
|
+
- Hints in the terminal and in the web app now name the short `mail-notify` command (`python -m ccmail` still works).
|
|
23
|
+
|
|
24
|
+
### Changed (summary)
|
|
25
|
+
- The summary in `summary` mode now covers the whole answer in at most 10 short lines: a lead, one line per
|
|
26
|
+
section ("Heading: what it opens with") sampled evenly across the message, and the closing question. The default
|
|
27
|
+
`summary_chars` is 300. The scrollable box of the HTML mail holds the complete answer (in `full` mode, the part
|
|
28
|
+
that is not already shown above it).
|
|
29
|
+
- `install-skill` and `install-hooks` also create the short `mail-notify` command, so there is no third step.
|
|
30
|
+
- Summary lines are short (about 130 characters; one bullet per section; a table is named by its first column),
|
|
31
|
+
the default `summary_chars` is 300 (the outcome fits in 300 characters), and the box no longer repeats the sentences the answer starts with.
|
|
32
|
+
- The scrollable box formats the Markdown of the answer: fenced code as a code block, inline code, bold, headings and
|
|
33
|
+
bullets, *italic*; bold may wrap inline code, and `full` mode formats the outcome too (everything escaped; the
|
|
34
|
+
plain-text part is unchanged).
|
|
35
|
+
|
|
36
|
+
### Added (command name)
|
|
37
|
+
- `mail-notify` is a command of its own (console script next to `ccmail`), and `install-cli` / `uninstall-cli` write
|
|
38
|
+
or remove a small launcher in a folder already on `PATH`, for setups where the Scripts folder is not (Microsoft
|
|
39
|
+
Store Python). The launcher only ever overwrites or deletes a file it wrote itself.
|
|
40
|
+
|
|
41
|
+
### Added (language)
|
|
42
|
+
- The web app is available in English: the `language` setting now covers the page, the mails and the command line.
|
|
43
|
+
`auto` follows Claude Code's own `language` setting (Italian/English), then the system language. Catalogs are
|
|
44
|
+
static files (`web/lang-en.js`, `web/lang-en-app.js`), applied without network access; a test checks that every
|
|
45
|
+
catalog entry still exists in the page.
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
- Several commands arriving in one pass (for example ON then OFF) are applied oldest first, so the project ends
|
|
49
|
+
in the state asked for last; before, the newest was applied first and the older one won.
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
- A verified reply that cannot be delivered (no chat tied to the mail, unknown or expired reference, delivery to
|
|
53
|
+
Claude switched off) is answered with a mail in the usual layout whose OUTCOME says "reply discarded" and why.
|
|
54
|
+
Capped at 5 an hour; ccmail's own mails are never answered, so no loop.
|
|
55
|
+
- `html_mail` setting (off by default): mails carry an HTML alternative next to the unchanged plain text, with
|
|
56
|
+
quick-reply buttons (OFF / ON / PAUSE 60, and "Write to Claude") that are pre-filled `mailto:` replies, and the
|
|
57
|
+
whole message in a box with its own scroll bar. The box follows `content_mode` (nothing extra in `title`/`minimal`).
|
|
58
|
+
One-click actions without pressing Send are not possible with a loopback-only daemon (Gmail actions need
|
|
59
|
+
registration with Google and a public HTTPS endpoint).
|
|
60
|
+
|
|
61
|
+
## 0.6.0
|
|
62
|
+
|
|
63
|
+
### Added
|
|
64
|
+
- **Cross-platform daemon**: data folders for Windows, macOS and Linux; credentials in Windows Credential Manager,
|
|
65
|
+
macOS Keychain or Linux Secret Service (password through stdin only), with an announced 0600-file fallback;
|
|
66
|
+
autostart through Task Scheduler, LaunchAgent or `systemd --user` (XDG autostart as last resort).
|
|
67
|
+
- Provider presets (Gmail, Outlook/Microsoft 365, iCloud, Yahoo, custom) in `ccmail init` and the web app.
|
|
68
|
+
- `language` setting (`auto`/`it`/`en`): mails, suggested commands, messages delivered to Claude and the command line.
|
|
69
|
+
`PAUSE` is accepted next to `PAUSA`.
|
|
70
|
+
- `device_name` setting: a `Device:` line in mails when ccmail runs on several computers.
|
|
71
|
+
- `ccmail doctor [--network]`: installation check that prints no secrets (credentials, hooks, interpreter, skill,
|
|
72
|
+
autostart, daemon, mail servers).
|
|
73
|
+
- `ccmail install-skill` / `uninstall-skill`: the `mail-notify` skill ships inside the package.
|
|
74
|
+
- `CLAUDE_CONFIG_DIR` is honoured for settings, skills, transcripts and the account name.
|
|
75
|
+
- The daemon picks a free port when the configured one is taken by another program.
|
|
76
|
+
- `docs/RESOURCES.md`: measured token, CPU, memory, disk, network and latency costs.
|
|
77
|
+
- Combination tests (`tests/test_matrix.py`): every mail kind x content mode x language x reply options, and policy vs. dry run.
|
|
78
|
+
- Community files, CI matrix (Windows/macOS/Linux x Python 3.10-3.13), release workflow with PyPI trusted publishing.
|
|
79
|
+
|
|
80
|
+
### Fixed
|
|
81
|
+
- The answer mail of an e-mail reply was still skipped as a "stop hook loop": the reply reaches the chat through a
|
|
82
|
+
Stop hook, so its turn ends with `stop_hook_active` set. The guard no longer applies to that one-shot turn.
|
|
83
|
+
- The IMAP scan fetched one header per message each pass; it now asks for all of them in a single command.
|
|
84
|
+
- The answer to a reply sent by e-mail was never mailed back when the turn was short or processes were still running
|
|
85
|
+
(the user, being away, never saw it). A turn started by an e-mail reply is now always mailed at its end, with the
|
|
86
|
+
subject "risposta alla tua email"; the per-project switch, snooze and hourly cap still apply.
|
|
87
|
+
|
|
88
|
+
### Changed
|
|
89
|
+
- Detached daemon start on POSIX uses a new session; `pythonw.exe` only on Windows.
|
|
90
|
+
- The design notes moved from the README to `docs/HOW-IT-WORKS.md`; working notes to `docs/dev/`.
|
|
91
|
+
|
|
92
|
+
## 0.5.0
|
|
93
|
+
|
|
94
|
+
### Added
|
|
95
|
+
- Reply-by-email control channel (`inbound_enabled`): `OFF` / `ON` / `PAUSA [min]` in the first line of a reply.
|
|
96
|
+
- Optional delivery of free-text replies into the Claude chat (`inbound_to_claude`, off by default).
|
|
97
|
+
- Mails are split into sections (outcome, background processes, suggested commands, management) and carry
|
|
98
|
+
`Chat: <name>`; account name comes from the Claude account.
|
|
99
|
+
- Usage-limit reached is its own event; summaries never cut a sentence in the middle.
|
|
100
|
+
- `ccmail diagnose` shows the reply channel state.
|
|
101
|
+
|
|
102
|
+
### Fixed
|
|
103
|
+
- Replies sent to oneself were rejected as unauthenticated: Gmail's Sent label is now used.
|
|
104
|
+
- The test mail was not recorded, so the app kept saying "SMTP not yet verified".
|
|
105
|
+
|
|
106
|
+
## 0.3.0
|
|
107
|
+
|
|
108
|
+
### Added
|
|
109
|
+
- Chat names: the daemon reads the Claude desktop app's per-chat metadata (title, project folder, archived flag, last activity, and the Claude Code session id). Mails show `Chat: <name>`; unknown chats show `Sessione: <8 characters>`.
|
|
110
|
+
- Title fallback: when app metadata is missing, the title is the latest `custom-title` record at the end of the session transcript (read from the tail of the file only, cached).
|
|
111
|
+
- Discovery of app metadata in both the Microsoft Store package location (`%LOCALAPPDATA%\Packages\Claude_*\LocalCache\Roaming\Claude`) and `%APPDATA%\Claude`, merged by session id. Override with `CCMAIL_CLAUDE_SESSIONS` (paths separated by `;`).
|
|
112
|
+
- Stable project resolution: chat folder, then a chat whose folder matches the transcript folder, then `CLAUDE_PROJECT_DIR`, then the shortest working folder seen for that transcript folder. Sub-folders are never projects; sub-agent transcripts are excluded.
|
|
113
|
+
- Startup repair: realigns stored sessions to their projects and removes projects with no sessions or chats; per-project switches are kept.
|
|
114
|
+
- Activity API: `GET /v1/projects?chats=N` (default 5 newest non-archived chats per project, `0` = all; archived chats listed separately) and `GET /v1/diagnostics`.
|
|
115
|
+
- `ccmail diagnose`: read-only report of what ccmail sees in the desktop app.
|
|
116
|
+
- Per-project on/off switch (`PUT /v1/projects/enabled`). When off, no mail and no wake-ups for that project. `ccmail status --cwd <folder>` reports it.
|
|
117
|
+
- Honest session states: working, waiting (only if recent), waiting for background processes, periodic check, quiet (idle over 6 h), ended, archived, stale, uncertain.
|
|
118
|
+
- `PostToolBatch` hook (async) for liveness.
|
|
119
|
+
|
|
120
|
+
### Changed
|
|
121
|
+
- Projects are derived from the chat's folder instead of the current working folder.
|
|
122
|
+
|
|
123
|
+
### Upgrade notes
|
|
124
|
+
- Run `ccmail install-hooks` again and restart Claude Code (the new `PostToolBatch` hook).
|
|
125
|
+
|
|
126
|
+
### Known limits
|
|
127
|
+
- The desktop app's metadata format is internal and undocumented. Every read soft-fails: if the format changes, ccmail falls back to hook data.
|
|
128
|
+
- A chat could in theory change its Claude Code session id after resume or compaction. Not observed so far.
|
|
129
|
+
|
|
130
|
+
## 0.2.0
|
|
131
|
+
|
|
132
|
+
### Added
|
|
133
|
+
- Background-task awareness: while tasks run in the background (`Stop.background_tasks`), intermediate `Stop` events send no mail.
|
|
134
|
+
- Periodic wake-up: every `heartbeat_minutes`, and only while tasks are still running, the daemon wakes Claude through an `asyncRewake` Stop hook (`ccmail wake`). Claude checks the tasks and its short reply is mailed as a status update. A `skill` mode is available where Claude schedules its own wake-ups.
|
|
135
|
+
- "Finished" mail: one mail when the last background task disappears.
|
|
136
|
+
- Watchdog rule: an optional silent-session watchdog (`watchdog_minutes`), which must be off or at least twice `heartbeat_minutes`.
|
|
137
|
+
- Summary mode (`content_mode = summary`, default): a short extractive summary of the turn, capped by `summary_chars`.
|
|
138
|
+
- Minimal mode (`content_mode = minimal`): no model-derived text at all.
|
|
139
|
+
- Account name (`account_name`), shown in every mail as `Account:`; empty = Windows user name.
|
|
140
|
+
|
|
141
|
+
## 0.1.0
|
|
142
|
+
|
|
143
|
+
### Added
|
|
144
|
+
- Local daemon for Windows that emails about Claude Code events through Gmail SMTP, with a local configuration web app on `127.0.0.1`.
|
|
145
|
+
- Hooks (`Stop`, `Notification`, `StopFailure`, `SessionStart`, `SessionEnd`, `UserPromptSubmit`, `PostToolUse` for `Bash`) forwarded to the daemon; all decisions are made in the daemon.
|
|
146
|
+
- Anti-spam gates: no confirmation mails, minimum turn length for `Stop`, mute after toggle, per-session cooldown, hourly cap, selectable `Notification` types, title-only mode.
|
|
147
|
+
- Credentials in Windows Credential Manager, with a DPAPI-encrypted file fallback.
|
|
148
|
+
- Commands: `init`, `on`, `off`, `status`, `test`, `open`, `stop`, `daemon`, `install-hooks`, `uninstall-hooks`, `migrate`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
Be respectful and constructive. Harassment, personal attacks and discrimination are not tolerated in issues, pull
|
|
4
|
+
requests or discussions. Assume good faith, be specific, and keep feedback about the work.
|
|
5
|
+
|
|
6
|
+
Maintainers may edit or remove contributions that break these rules and may restrict participation. Report problems
|
|
7
|
+
privately through the repository's security advisory form or by contacting the maintainer on GitHub.
|
|
8
|
+
|
|
9
|
+
This is a short statement in the spirit of the Contributor Covenant 2.1.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for helping. ccmail is deliberately small: standard library only, no runtime dependencies. Please keep it so.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone https://github.com/lobascioemanuele-droid/mail-notify && cd mail-notify
|
|
9
|
+
python -m venv .venv && . .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
10
|
+
pip install -e .
|
|
11
|
+
PYTHONPATH=src:tests python -W ignore -m unittest discover -s tests # Windows: set PYTHONPATH=src;tests
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`tests/test_matrix.py` runs every mail kind x content mode x language x reply option and compares the
|
|
15
|
+
policy with the web app's dry run: extend its tables when you add a kind, a mode or a setting.
|
|
16
|
+
|
|
17
|
+
Tests isolate the data folder (`CCMAIL_HOME`), the desktop-app metadata (`CCMAIL_CLAUDE_SESSIONS`) and the account
|
|
18
|
+
name, and pin `CCMAIL_LANG=it`. They never touch your real credentials or `~/.claude`.
|
|
19
|
+
|
|
20
|
+
## Rules
|
|
21
|
+
|
|
22
|
+
- **Never** handle a real password in code, tests, logs or issues. Use fake values.
|
|
23
|
+
- No personal paths, names or addresses in code, tests or docs.
|
|
24
|
+
- Texts a user can see exist in Italian and English: write them as an `(it, en)` pair with `i18n.pick`.
|
|
25
|
+
- Anything that can send mail, or can reach Claude, needs a test for the case where it must *not* happen.
|
|
26
|
+
- Platform code goes through the small seams that already exist (`paths`, `credstore`, `autostart`) so it can be tested
|
|
27
|
+
on any OS.
|
|
28
|
+
- Update `CHANGELOG.md`. Add docs when behaviour changes, and `docs/RESOURCES.md` when a change alters what ccmail
|
|
29
|
+
sends to the model, polls, or runs.
|
|
30
|
+
|
|
31
|
+
## Security issues
|
|
32
|
+
|
|
33
|
+
Report them privately: see [SECURITY.md](SECURITY.md).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mail-notify contributors
|
|
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,388 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mail-notify
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Local daemon that e-mails you about Claude Code events, with a local settings web app.
|
|
5
|
+
Author: mail-notify contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/lobascioemanuele-droid/mail-notify
|
|
8
|
+
Project-URL: Source, https://github.com/lobascioemanuele-droid/mail-notify
|
|
9
|
+
Project-URL: Issues, https://github.com/lobascioemanuele-droid/mail-notify/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/lobascioemanuele-droid/mail-notify/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: claude,claude-code,notifications,email,hooks,smtp
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Environment :: Web Environment
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Operating System :: MacOS
|
|
18
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Communications :: Email
|
|
25
|
+
Classifier: Topic :: Software Development
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# mail-notify
|
|
32
|
+
|
|
33
|
+
**E-mail notifications for [Claude Code](https://code.claude.com), with a local settings web app.**
|
|
34
|
+
A small daemon on your own computer mails you when Claude finishes a long task, needs a permission, hits an error or a
|
|
35
|
+
usage limit, or when your background processes finish. You can answer from your phone by replying to the mail.
|
|
36
|
+
|
|
37
|
+
*Naming: the package and the command are `mail-notify`; the daemon and the Python module inside are called `ccmail`
|
|
38
|
+
(`ccmail` also works as a command).*
|
|
39
|
+
|
|
40
|
+
[](https://github.com/lobascioemanuele-droid/mail-notify/actions/workflows/ci.yml)
|
|
41
|
+

|
|
42
|
+

|
|
43
|
+

|
|
44
|
+
|
|
45
|
+
- **Few mails, on purpose.** Every mail passes several gates (turn length, cooldown, hourly cap, event types). Turning
|
|
46
|
+
it on or off sends nothing.
|
|
47
|
+
- **Private by design.** Standard library only, no telemetry. The only thing that leaves your machine is the mail
|
|
48
|
+
itself, sent through your own mail account. The password lives in your system's credential store.
|
|
49
|
+
- **Reply to act.** Reply `OFF`, `ON` or `PAUSE 60` to a notification or, if you enable it, write a sentence that is
|
|
50
|
+
delivered to the Claude chat that sent the mail.
|
|
51
|
+
- **Understands your chats.** Mails name the project, the chat and your Claude account, and carry a short summary of
|
|
52
|
+
what Claude said (about 300 characters, at most 10 lines; the complete answer follows in a scrollable box).
|
|
53
|
+
- **Works while you are away.** While background tasks run, Claude is woken at a period you choose, only when tasks
|
|
54
|
+
exist, to report their status.
|
|
55
|
+
|
|
56
|
+
> **Tested today: Windows with Gmail.** macOS, Linux and mail providers other than Gmail (the Outlook, iCloud and
|
|
57
|
+
> Yahoo presets) are implemented and covered by the automated tests in CI, but are **not yet tested on a real
|
|
58
|
+
> system: that is planned for a later release.** Please report what you find.
|
|
59
|
+
|
|
60
|
+
## Install step by step
|
|
61
|
+
|
|
62
|
+
You need your mail account's app password and about fifteen minutes. Run every command in **your own terminal**.
|
|
63
|
+
ccmail never asks Claude for your password.
|
|
64
|
+
|
|
65
|
+
### Requirements
|
|
66
|
+
|
|
67
|
+
- Python 3.10 or newer.
|
|
68
|
+
- [Claude Code](https://code.claude.com), as the CLI or the desktop app.
|
|
69
|
+
- A mail account that allows SMTP with an **app password**. Presets exist for Gmail, Outlook, iCloud and Yahoo, and
|
|
70
|
+
you can enter any SMTP server.
|
|
71
|
+
- Optional, for replies by mail: IMAP access to the same mailbox.
|
|
72
|
+
|
|
73
|
+
### Step 1: install the package
|
|
74
|
+
|
|
75
|
+
Install with [pipx](https://pipx.pypa.io), which keeps the program in its own environment. Once mail-notify is on PyPI:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pipx install mail-notify
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
To install the current version from the repository instead:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pipx install git+https://github.com/lobascioemanuele-droid/mail-notify
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
With Microsoft Store Python the `mail-notify` command can be "not found", because its Scripts folder is not on
|
|
88
|
+
`PATH`. In that case use `python -m ccmail` instead of `mail-notify` in every step below, or run
|
|
89
|
+
`python -m ccmail install-cli` once: it creates a short `mail-notify` command in a folder that is already on `PATH`.
|
|
90
|
+
|
|
91
|
+
### Step 2: set up the sender account
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
mail-notify init
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The prompts ask for the sender address, the provider (the right app-password link is shown for it), the app password,
|
|
98
|
+
the recipient (it defaults to the sender) and an optional device name. The app password is typed in a **hidden**
|
|
99
|
+
prompt and stored only in the system credential store (Windows Credential Manager, macOS Keychain or Linux Secret
|
|
100
|
+
Service). Never paste it into a chat, a file or an issue. This step sends no mail.
|
|
101
|
+
|
|
102
|
+
Where to create an app password:
|
|
103
|
+
|
|
104
|
+
- Gmail: https://myaccount.google.com/apppasswords (needs 2-step verification)
|
|
105
|
+
- Outlook: https://account.live.com/proofs/AppPassword
|
|
106
|
+
- iCloud: https://account.apple.com/account/manage
|
|
107
|
+
- Yahoo: https://login.yahoo.com/account/security
|
|
108
|
+
|
|
109
|
+
### Step 3: install the hooks, the skill and the short command
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
mail-notify setup --autostart
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
One command does all of this, and sends nothing:
|
|
116
|
+
|
|
117
|
+
- adds ccmail's hooks to Claude Code's settings (a backup of the file is made first);
|
|
118
|
+
- copies the `/mail-notify` skill to `~/.claude/skills/mail-notify/`;
|
|
119
|
+
- creates the short `mail-notify` command if it is missing;
|
|
120
|
+
- with `--autostart`, starts the background daemon at logon.
|
|
121
|
+
|
|
122
|
+
It never asks for the password (that is `init`, step 2) and can be run again at any time. The three parts are also
|
|
123
|
+
available one by one: `install-hooks`, `install-skill` and `install-cli`.
|
|
124
|
+
|
|
125
|
+
### Step 4: restart Claude Code
|
|
126
|
+
|
|
127
|
+
Close and reopen Claude Code: hooks and skills are read at startup. After that `/mail-notify` appears in the list of
|
|
128
|
+
skills.
|
|
129
|
+
|
|
130
|
+
### Step 5: turn it on and test it
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
mail-notify on
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Notifications are **off** after installation, so nothing is sent until you run this. `on` sends no mail.
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
mail-notify test
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
This sends **one** test mail. It also works while notifications are off.
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
mail-notify open
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
This opens the settings web app in your browser. It listens on `127.0.0.1` only and uses a one-time sign-in link.
|
|
149
|
+
Optionally, in its **Rules** tab, turn on:
|
|
150
|
+
|
|
151
|
+
- **Control by replying to the e-mail** (reply `OFF` / `ON` / `PAUSE`; needs IMAP, see above).
|
|
152
|
+
- **Replies reach Claude** (free-text replies are delivered to the chat that sent the mail).
|
|
153
|
+
- **E-mail with buttons** (an HTML version with tappable quick-reply cards).
|
|
154
|
+
|
|
155
|
+
### Step 6: use it
|
|
156
|
+
|
|
157
|
+
In Claude Code, the `/mail-notify` skill accepts `on`, `off`, `status`, `test` and `open`:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
/mail-notify on
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
/mail-notify status
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Each project also has its own switch in the web app's **Activity** tab.
|
|
168
|
+
|
|
169
|
+
When reply control is on, each mail tells you how to reply. Write one of these on the **first line** of your reply:
|
|
170
|
+
|
|
171
|
+
- `OFF`: turns notifications off for that project.
|
|
172
|
+
- `ON`: turns them back on for that project.
|
|
173
|
+
- `PAUSE 60` (or `SLEEP`, or `PAUSE` alone for 60 minutes): silence on all projects for that many minutes.
|
|
174
|
+
|
|
175
|
+
### Updating
|
|
176
|
+
|
|
177
|
+
Update the package (use the command that matches how you installed it):
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
pipx upgrade mail-notify
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
pipx install --force git+https://github.com/lobascioemanuele-droid/mail-notify
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Then refresh the hooks and the skill, and restart Claude Code:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
mail-notify setup
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Uninstalling
|
|
194
|
+
|
|
195
|
+
Stop the daemon, then remove the pieces in this order:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
mail-notify stop
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
mail-notify uninstall-hooks --autostart
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
mail-notify uninstall-skill
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
mail-notify uninstall-cli
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
pipx uninstall mail-notify
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Uninstalling does not delete the data folder or the stored password. Delete the data folder yourself (on Windows
|
|
218
|
+
`%APPDATA%\ccmail`), remove the ccmail entry from your credential store (on Windows it is `ccmail:smtp` in Credential
|
|
219
|
+
Manager), and revoke the app password at your mail provider.
|
|
220
|
+
|
|
221
|
+
## What a mail looks like
|
|
222
|
+
|
|
223
|
+
- **Header:** `Project:`, `Account:` and `Chat:` (the chat's name in the desktop app, or `Session:` with a short id when
|
|
224
|
+
the name is unknown). `Device:` appears when you set a device name. Turn duration and type follow for a finished turn.
|
|
225
|
+
- **OUTCOME** (`ESITO` in Italian): a short summary of the whole answer, about 300 characters (the `summary_chars`
|
|
226
|
+
setting) and at most 10 lines: a lead, one line per section, and the closing question when the answer ends with one.
|
|
227
|
+
With HTML mail on, the complete answer is in a scrollable box below it.
|
|
228
|
+
- **Background processes:** how many are still running, and their ids, when there are any.
|
|
229
|
+
- **Suggested commands:** a numbered list tailored to the message, to paste into the chat. They are tappable boxes
|
|
230
|
+
when "Replies reach Claude" is on.
|
|
231
|
+
- **Quick-reply cards** (HTML mail with reply control on): `OFF`, `ON` and `SLEEP` (a 1-hour pause on all projects),
|
|
232
|
+
other pauses (15 minutes, 3 hours, 8 hours) and, if "Replies reach Claude" is on, "Write to Claude". Each card opens
|
|
233
|
+
a pre-filled reply; you still press Send.
|
|
234
|
+
- **Reply instructions** in plain text, when reply control is on.
|
|
235
|
+
|
|
236
|
+
## What you get
|
|
237
|
+
|
|
238
|
+
| Mail | When |
|
|
239
|
+
|---|---|
|
|
240
|
+
| Task finished | A turn longer than 2 minutes (setting) ended |
|
|
241
|
+
| Needs your input | A permission prompt or a question is waiting |
|
|
242
|
+
| Error / usage limit | An API error, or the account limit was reached (with the reset time) |
|
|
243
|
+
| Background processes | Periodic status while they run; one mail when the last one ends |
|
|
244
|
+
| Chat stalled | Optional watchdog: no events for N minutes with tasks declared |
|
|
245
|
+
|
|
246
|
+
### HTML mail (optional)
|
|
247
|
+
|
|
248
|
+
Turn on **E-mail with buttons** in the web app's Rules tab and every mail also carries an HTML version next to the
|
|
249
|
+
unchanged plain text: a coloured header by event type, the data in a grid, quick-reply cards, the suggested commands
|
|
250
|
+
as tappable boxes (when "Replies reach Claude" is on), and the whole message in a box with its own scroll bar (at most
|
|
251
|
+
340 px tall). The same layout is used for the confirmation, "command executed" and "reply discarded" mails.
|
|
252
|
+
|
|
253
|
+
- The cards are pre-filled replies (`mailto:`): you still press Send. One-click actions inside Gmail need registration
|
|
254
|
+
with Google and a public HTTPS endpoint, which a daemon listening on loopback cannot offer.
|
|
255
|
+
- A mail app that does not show HTML shows the plain text, without cards or the scroll box.
|
|
256
|
+
- Nothing extra leaves your machine: the box follows the content mode (`title` and `minimal` add nothing), and text
|
|
257
|
+
goes through the same secret redaction.
|
|
258
|
+
- A verified reply that cannot be delivered (no chat tied to the mail, unknown or expired reference, delivery to
|
|
259
|
+
Claude switched off) is answered with a mail whose outcome says "reply discarded" and why. At most 5 an hour.
|
|
260
|
+
|
|
261
|
+
### Settings web app
|
|
262
|
+
|
|
263
|
+
`mail-notify open` shows: an **Overview** with health checks and the main on/off switch; **Rules**
|
|
264
|
+
(what to report, how often, how much text, reply control); **Activity** with per-project switches and the latest chats;
|
|
265
|
+
and **Connection** with provider presets, the device name and a test button. It listens on `127.0.0.1` only, with a
|
|
266
|
+
one-time sign-in link, CSRF protection and a strict content security policy.
|
|
267
|
+
|
|
268
|
+
The page, the mails and the command line follow the `language` setting: `auto` (the default), `it` or `en`. `auto`
|
|
269
|
+
uses the language set in Claude Code's own `language` setting when it names Italian or English, and otherwise the
|
|
270
|
+
system language. The page is translated from static catalogs in the package. Nothing is sent to a translation
|
|
271
|
+
service, and data from the daemon (project names, chat titles, mail subjects) is never translated.
|
|
272
|
+
|
|
273
|
+
### Reply by mail
|
|
274
|
+
|
|
275
|
+
Off by default (web app → Rules → *Control by replying to the e-mail*). When on, ccmail reads your mailbox over IMAP
|
|
276
|
+
and accepts only replies that come from you, are verified by the provider, quote a valid reference from a recent
|
|
277
|
+
ccmail mail, and start with `OFF`, `ON` or `PAUSE [minutes]` (the Italian words `SPEGNI`, `ACCENDI`, `PAUSA`, and a few
|
|
278
|
+
others are accepted too). With **Replies reach Claude** on, any other reply text is delivered to the chat the mail came
|
|
279
|
+
from. The answer to it is mailed back to you when that turn ends, even if it was short, with the subject
|
|
280
|
+
"answer to your e-mail" (Italian: "risposta alla tua email").
|
|
281
|
+
|
|
282
|
+
See [SECURITY.md](SECURITY.md) for the exact rules and [docs/HOW-IT-WORKS.md](docs/HOW-IT-WORKS.md) for the delivery
|
|
283
|
+
paths.
|
|
284
|
+
|
|
285
|
+
### The `/mail-notify` skill
|
|
286
|
+
|
|
287
|
+
`mail-notify install-skill` copies a small skill to `~/.claude/skills/mail-notify/`. It lets you type
|
|
288
|
+
`/mail-notify on`, `off`, `status`, `test` or `open` in Claude Code, and tells Claude how to behave during long
|
|
289
|
+
processes (a concise status while they run, a short final summary). The skill is instructed never to read credential
|
|
290
|
+
files or edit Claude's settings.
|
|
291
|
+
|
|
292
|
+
## Several computers
|
|
293
|
+
|
|
294
|
+
Every computer has its own daemon, data folder and credentials, and its own copy of the settings. Give each one a name
|
|
295
|
+
(`mail-notify init`, or web app → Connection → *Name of this device*) and it appears as `Device:` in the mails. A reply
|
|
296
|
+
is applied by the daemon that sent the mail it refers to; daemons ignore references they did not issue.
|
|
297
|
+
|
|
298
|
+
## Platforms
|
|
299
|
+
|
|
300
|
+
| | Windows | macOS | Linux |
|
|
301
|
+
|---|---|---|---|
|
|
302
|
+
| Data folder | `%APPDATA%\ccmail` | `~/Library/Application Support/ccmail` | `~/.config/ccmail` |
|
|
303
|
+
| Password store | Credential Manager (fallback: DPAPI file) | Keychain (fallback: 0600 file) | Secret Service via `secret-tool` (fallback: 0600 file) |
|
|
304
|
+
| Start at logon | Task Scheduler | LaunchAgent | `systemd --user` (fallback: XDG autostart) |
|
|
305
|
+
| Status | tested | to be tested (later release) | to be tested (later release) |
|
|
306
|
+
|
|
307
|
+
The fallback files are announced loudly by `init`, `status`, `doctor` and the web app. On Linux install
|
|
308
|
+
`libsecret-tools` to use the Secret Service. Claude Code's folder honours `CLAUDE_CONFIG_DIR`.
|
|
309
|
+
|
|
310
|
+
## Privacy and security
|
|
311
|
+
|
|
312
|
+
- ccmail reads only what it needs: what Claude Code passes to its hooks; the desktop app's chat titles and folders; the
|
|
313
|
+
latest chat title recorded at the end of a session transcript, when the app's metadata is missing; the account's
|
|
314
|
+
display name from `~/.claude.json` (nothing else in that file is used); and the `language` key of Claude Code's
|
|
315
|
+
`settings.json`. Installing the hooks edits that file, after making a backup.
|
|
316
|
+
- Message text is redacted for common secrets before it is put in a mail. `title` and `minimal` content modes send no
|
|
317
|
+
model-written text at all.
|
|
318
|
+
- The password is never written to logs, the database, the repository or the web app; the API never returns it.
|
|
319
|
+
- The daemon binds to `127.0.0.1`, authenticates hooks with a file-based token and checks `Host` and `Origin`.
|
|
320
|
+
|
|
321
|
+
Details and the threat model: [SECURITY.md](SECURITY.md). Report vulnerabilities privately (see that file).
|
|
322
|
+
|
|
323
|
+
## Troubleshooting
|
|
324
|
+
|
|
325
|
+
| Symptom | Try |
|
|
326
|
+
|---|---|
|
|
327
|
+
| No mail at all | `mail-notify status`: notifications start OFF, so run `mail-notify on`. Then `mail-notify doctor --network` and `mail-notify test`; check spam and the app password |
|
|
328
|
+
| Hooks do nothing | `mail-notify install-hooks`, then restart Claude Code; `mail-notify doctor` flags a moved Python |
|
|
329
|
+
| No buttons or cards in a mail | Cards need HTML mail and reply control on; plain-text mail apps show no buttons |
|
|
330
|
+
| Chat names missing | `mail-notify diagnose` shows where the desktop app's metadata is looked for |
|
|
331
|
+
| Port busy | The daemon picks a free port by itself; hooks and CLI read it from `daemon.json` |
|
|
332
|
+
| Reply by mail is slow | Most of the delay is the provider delivering mail to yourself (seconds to about 25 s) |
|
|
333
|
+
| Linux: password in a file | Install `libsecret-tools` and run `mail-notify init` again |
|
|
334
|
+
|
|
335
|
+
## Commands
|
|
336
|
+
|
|
337
|
+
`init` · `setup [--autostart]` · `on` · `off` · `status [--cwd DIR]` · `test` · `open` · `stop` · `doctor [--network]` · `install-hooks
|
|
338
|
+
[--autostart]` · `uninstall-hooks [--autostart]` · `install-skill` · `uninstall-skill` · `diagnose` · `trace on|off|show`
|
|
339
|
+
· `migrate` · `daemon [--foreground]` · `install-cli` · `uninstall-cli`.
|
|
340
|
+
|
|
341
|
+
The command is installed as both `mail-notify` and `ccmail`. `install-hooks` and `install-skill` also create the short
|
|
342
|
+
`mail-notify` command. If neither name is found, run `python -m ccmail install-cli` once (see Step 1).
|
|
343
|
+
|
|
344
|
+
## Resource use
|
|
345
|
+
|
|
346
|
+
No model calls at all in normal operation. The daemon uses about 35 MB and about 0.05–0.25 % of one core, and each hook
|
|
347
|
+
call adds about 0.12 s. Tokens are spent only on the skill text, on periodic wake-ups while background tasks run and on
|
|
348
|
+
delivered replies. The HTML mail costs no tokens (about 0.1–0.6 ms of CPU and 6–9 KB more per mail). Measurements,
|
|
349
|
+
formulas and limits: [docs/RESOURCES.md](docs/RESOURCES.md).
|
|
350
|
+
|
|
351
|
+
## How it works
|
|
352
|
+
|
|
353
|
+
Hooks run a tiny client (`python -m ccmail hook`) that forwards the event to the local daemon and always exits 0. The
|
|
354
|
+
daemon applies the rules, writes to SQLite, and an outbox worker sends mail with retries. More in
|
|
355
|
+
[docs/HOW-IT-WORKS.md](docs/HOW-IT-WORKS.md).
|
|
356
|
+
|
|
357
|
+
## Development
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
git clone https://github.com/lobascioemanuele-droid/mail-notify && cd mail-notify
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
python -m venv .venv
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
. .venv/bin/activate
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
On Windows, activate with `.venv\Scripts\activate` instead.
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
pip install -e .
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
python -W ignore -m unittest discover -s tests
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Set `PYTHONPATH=src:tests` first if the package is not installed.
|
|
382
|
+
|
|
383
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md). Changes are listed in [CHANGELOG.md](CHANGELOG.md); what 1.x promises to keep
|
|
384
|
+
stable is in [docs/STABILITY.md](docs/STABILITY.md).
|
|
385
|
+
|
|
386
|
+
## License
|
|
387
|
+
|
|
388
|
+
[MIT](LICENSE).
|