claude-magic-link 0.2.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.
@@ -0,0 +1,126 @@
1
+ name: Lint
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ syntax:
11
+ name: Syntax Check
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: actions/setup-python@v5
16
+ with:
17
+ python-version: "3.14"
18
+ allow-prereleases: true
19
+ - name: Compile all modules
20
+ run: python -m py_compile src/claude_magic_link/*.py
21
+ - name: Import check
22
+ run: |
23
+ python -c "import claude_magic_link; print('OK: ' + claude_magic_link.__version__)"
24
+ env:
25
+ PYTHONPATH: src
26
+ - name: CLI responds
27
+ run: python -m claude_magic_link --version
28
+ env:
29
+ PYTHONPATH: src
30
+
31
+ tests:
32
+ name: Tests
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+ - uses: actions/setup-python@v5
37
+ with:
38
+ python-version: "3.14"
39
+ allow-prereleases: true
40
+ - name: Install pytest
41
+ run: python -m pip install pytest
42
+ - name: Run tests
43
+ run: pytest -q
44
+
45
+ ruff:
46
+ name: Ruff
47
+ runs-on: ubuntu-latest
48
+ steps:
49
+ - uses: actions/checkout@v4
50
+ - uses: actions/setup-python@v5
51
+ with:
52
+ python-version: "3.14"
53
+ allow-prereleases: true
54
+ - name: Install ruff
55
+ run: python -m pip install ruff
56
+ - name: Ruff check
57
+ run: ruff check .
58
+
59
+ version:
60
+ name: Version Consistency
61
+ runs-on: ubuntu-latest
62
+ steps:
63
+ - uses: actions/checkout@v4
64
+ - name: pyproject.toml == __init__.py
65
+ run: |
66
+ V_PYPROJECT=$(grep -m1 'version' pyproject.toml | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
67
+ V_INIT=$(grep '__version__' src/claude_magic_link/__init__.py | grep -oE '[0-9]+\.[0-9]+\.[0-9]+')
68
+ echo "pyproject.toml: $V_PYPROJECT"
69
+ echo "__init__.py: $V_INIT"
70
+ [ "$V_PYPROJECT" = "$V_INIT" ] || { echo "::error::Version mismatch"; exit 1; }
71
+
72
+ links:
73
+ name: Markdown Links
74
+ runs-on: ubuntu-latest
75
+ steps:
76
+ - uses: actions/checkout@v4
77
+ - name: Check relative links
78
+ run: |
79
+ python3 -c "
80
+ import re, os, glob, sys
81
+ bad = []
82
+ for f in glob.glob('*.md') + glob.glob('**/*.md', recursive=True):
83
+ base = os.path.dirname(f)
84
+ for m in re.finditer(r'\[[^\]]*\]\(([^)#\s]+)\)', open(f).read()):
85
+ t = m.group(1)
86
+ if t.startswith(('http', 'mailto:')):
87
+ continue
88
+ if not os.path.exists(os.path.normpath(os.path.join(base, t))):
89
+ bad.append((f, t))
90
+ if bad:
91
+ for f, t in bad:
92
+ print(f'::error file={f}::Broken link: {t}')
93
+ sys.exit(1)
94
+ print('All relative links OK')
95
+ "
96
+
97
+ changelog:
98
+ name: Changelog Links
99
+ runs-on: ubuntu-latest
100
+ steps:
101
+ - uses: actions/checkout@v4
102
+ - name: Every version has a link definition
103
+ run: |
104
+ if [ ! -f CHANGELOG.md ]; then
105
+ echo "No CHANGELOG.md yet, skipping"
106
+ exit 0
107
+ fi
108
+ FAIL=0
109
+ for v in $(grep -oE '^## \[[0-9]+\.[0-9]+\.[0-9]+\]' CHANGELOG.md | tr -d '#[] '); do
110
+ if ! grep -q "^\[$v\]:" CHANGELOG.md; then
111
+ echo "::error file=CHANGELOG.md::Missing link definition for $v"
112
+ FAIL=1
113
+ fi
114
+ done
115
+ exit $FAIL
116
+
117
+ gitleaks:
118
+ name: Secrets Scan
119
+ runs-on: ubuntu-latest
120
+ steps:
121
+ - uses: actions/checkout@v4
122
+ with:
123
+ fetch-depth: 0
124
+ - uses: gitleaks/gitleaks-action@v2
125
+ env:
126
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,38 @@
1
+ name: Publish
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build:
9
+ name: Build
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: "3.14"
16
+ allow-prereleases: true
17
+ - name: Install build
18
+ run: python -m pip install build
19
+ - name: Build sdist and wheel
20
+ run: python -m build
21
+ - uses: actions/upload-artifact@v4
22
+ with:
23
+ name: dist
24
+ path: dist/
25
+
26
+ publish:
27
+ name: Publish to PyPI
28
+ needs: build
29
+ runs-on: ubuntu-latest
30
+ environment: pypi
31
+ permissions:
32
+ id-token: write
33
+ steps:
34
+ - uses: actions/download-artifact@v4
35
+ with:
36
+ name: dist
37
+ path: dist/
38
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,44 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+
9
+ # Virtual environments
10
+ venv/
11
+ .venv/
12
+
13
+ # Linter / type-checker caches
14
+ .ruff_cache/
15
+ .mypy_cache/
16
+
17
+ # dotenv environment variable files
18
+ .env
19
+ .env.*
20
+
21
+ # Runtime config (contains IMAP credentials)
22
+ config.toml
23
+
24
+ # Logs
25
+ *.log
26
+
27
+ # Personal / marketing content (not part of the project)
28
+ *_POST.md
29
+
30
+ # Private working instructions for Claude Code, not for the public repo
31
+ CLAUDE.md
32
+
33
+ # Local agent tooling (slash commands, session settings)
34
+ .claude/
35
+
36
+ # IDE / tooling
37
+ .idea/
38
+ .vscode/
39
+ *.swp
40
+ *.swo
41
+ *~
42
+
43
+ # OS
44
+ .DS_Store
@@ -0,0 +1,74 @@
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.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.2.0] - 2026-08-29
11
+
12
+ ### Security
13
+ - **Exact token matching in DMARC/DKIM result parsing** -- the previous
14
+ substring match accepted lookalike domains such as
15
+ `header.d=mail.anthropic.com.evil.com` as a pass for `mail.anthropic.com`
16
+ - **Strict base64 validation for the recipient address in the link fragment**
17
+ -- invalid input previously decoded to an empty string instead of being
18
+ rejected; URL-safe base64 is now decoded correctly
19
+ - Startup warning when the config file contains an inline password but is
20
+ readable by other users
21
+ - State, log, and lock directories are created with mode 0700
22
+
23
+ ### Fixed
24
+ - Mails whose INTERNALDATE is slightly in the future (server clock ahead of
25
+ the local clock) are no longer dropped; skew up to 120 s is clamped so
26
+ links open reliably
27
+ - IMAP SEARCH filter now derives from `trusted_sender_domains` instead of a
28
+ hardcoded domain, so custom sender domains are actually found
29
+ - Duplicate error output when the config file is missing
30
+ - macOS notifications now render non-ASCII text correctly
31
+ - Mails without a decodable body no longer raise an unhandled error
32
+ - Lock file is no longer unlinked on shutdown, closing a race in which two
33
+ instances could hold locks on different inodes of the same path
34
+
35
+ ### Added
36
+ - **Test suite** (pytest, 71 tests) covering the security checks including
37
+ the documented attack cases, IMAP helpers, config loading, and state
38
+ persistence
39
+ - **Ruff lint job and test job** in CI
40
+ - **PyPI publishing workflow** via GitHub Actions Trusted Publishing,
41
+ triggered by published GitHub releases
42
+ - Ruff configuration and dev dependency group in `pyproject.toml`
43
+ - FAQ entries: why not stay logged in, Windows support status
44
+
45
+ ### Removed
46
+ - Internal agent tooling (`.claude/`) from the public repository
47
+
48
+ ## [0.1.0] - 2026-08-29
49
+
50
+ ### Added
51
+ - **IMAP IDLE watcher** for real-time magic link detection (Python 3.14+ `IMAP4.idle()` API)
52
+ - **Multi-account support** with per-account browser mapping (macOS bundle IDs and Linux commands)
53
+ - **6-layer security model**: sender domain, HTTPS, host, path, recipient matching, DMARC/DKIM verification
54
+ - **Per-result DKIM parsing** preventing cross-domain authentication bypass
55
+ - **Recipient validation** rejecting links with undecodable fragment addresses
56
+ - **Atomic deduplication** via `_try_reserve()` preventing race conditions across IMAP workers
57
+ - **Daemon mode** with auto-reconnect, exponential backoff, and graceful shutdown (SIGINT/SIGTERM)
58
+ - **Single-scan mode** (`--once`) for cron-style operation
59
+ - **Dry-run mode** (`--dry-run`) for safe testing
60
+ - **Desktop notifications** via osascript (macOS) and notify-send (Linux)
61
+ - **Single-instance locking** via flock in a user-private directory
62
+ - **Rotating log files** (1 MB, 3 backups) in platform-specific locations
63
+ - **Atomic state persistence** with temp-file write and rename
64
+ - **Zero dependencies** -- pure Python standard library
65
+ - **CLI** with argparse (`--config`, `--once`, `--dry-run`, `--max-age`, `--verbose`, `--version`)
66
+ - **Platform-aware paths** for config, state, logs, and lock files (macOS/Linux)
67
+ - **py.typed marker** for downstream type checking
68
+ - **CI** with 5 jobs: syntax check, version consistency, markdown links, changelog links, gitleaks
69
+ - **LaunchAgent and systemd examples** in README
70
+ - **Security documentation** (SECURITY.md) with threat model and recommendations
71
+
72
+ [Unreleased]: https://github.com/fidpa/claude-magic-link/compare/v0.2.0...HEAD
73
+ [0.2.0]: https://github.com/fidpa/claude-magic-link/compare/v0.1.0...v0.2.0
74
+ [0.1.0]: https://github.com/fidpa/claude-magic-link/releases/tag/v0.1.0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marc Allgeier
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,329 @@
1
+ Metadata-Version: 2.5
2
+ Name: claude-magic-link
3
+ Version: 0.2.0
4
+ Summary: Auto-open Claude.ai magic links from your inbox via IMAP IDLE
5
+ Project-URL: Homepage, https://github.com/fidpa/claude-magic-link
6
+ Project-URL: Repository, https://github.com/fidpa/claude-magic-link
7
+ Project-URL: Issues, https://github.com/fidpa/claude-magic-link/issues
8
+ Author-email: Marc Allgeier <webinar@arni-gmbh.de>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: anthropic,authentication,claude,imap,magic-link
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Communications :: Email
20
+ Classifier: Topic :: Security
21
+ Classifier: Topic :: Utilities
22
+ Requires-Python: >=3.14
23
+ Description-Content-Type: text/markdown
24
+
25
+ # Claude Magic Link
26
+
27
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
28
+ [![Python](https://img.shields.io/badge/Python-3.14%2B-blue?logo=python)](https://www.python.org/)
29
+ [![Platform](https://img.shields.io/badge/platform-macOS%20%7C%20Linux-lightgrey)]()
30
+ [![No Dependencies](https://img.shields.io/badge/dependencies-none-brightgreen)]()
31
+ ![Last Commit](https://img.shields.io/github/last-commit/fidpa/claude-magic-link)
32
+
33
+ Auto-open Claude.ai magic links from your inbox.
34
+
35
+ **The problem**: Claude.ai uses passwordless "magic link" authentication. Every login sends an email, you open it, find the link, click it, confirm. With multiple accounts this gets tedious fast.
36
+
37
+ **The solution**: claude-magic-link watches your IMAP mailbox via IDLE (push-based, near-instant) and opens the magic link in the right browser automatically. You just confirm in the browser.
38
+
39
+ ## Features
40
+
41
+ - **IMAP IDLE** -- push-based, opens links within seconds of arrival
42
+ - **Multi-Account** -- watch multiple mailboxes, each with its own browser
43
+ - **Security-First** -- 6-layer verification (sender, HTTPS, host, path, recipient match, DMARC/DKIM)
44
+ - **Zero Dependencies** -- pure Python standard library (3.14+)
45
+ - **Read-Only** -- never modifies, moves, or deletes your mail
46
+ - **Platform-Aware** -- macOS (`open -b`) and Linux (`xdg-open`) support
47
+ - **Desktop Notifications** -- macOS and Linux (notify-send)
48
+ - **Single Instance** -- flock-based lock prevents duplicate opens
49
+ - **Daemon Mode** -- run continuously with auto-reconnect and exponential backoff
50
+
51
+ ## Quick Start
52
+
53
+ ### Prerequisites
54
+
55
+ - Python 3.14+ (for the [IMAP IDLE API](https://docs.python.org/3.14/library/imaplib.html#imaplib.IMAP4.idle))
56
+ - An IMAP mailbox that receives Claude.ai login emails
57
+ - IMAP server with IDLE support (most providers have this)
58
+
59
+ ### Install
60
+
61
+ ```bash
62
+ # From PyPI
63
+ pipx install claude-magic-link
64
+
65
+ # Or from source
66
+ git clone https://github.com/fidpa/claude-magic-link.git
67
+ cd claude-magic-link
68
+ pip install .
69
+ ```
70
+
71
+ ### Configure
72
+
73
+ ```bash
74
+ # Copy the example config to the platform-specific location
75
+ # macOS:
76
+ mkdir -p ~/Library/Application\ Support/claude-magic-link
77
+ cp config.example.toml ~/Library/Application\ Support/claude-magic-link/config.toml
78
+
79
+ # Linux:
80
+ mkdir -p ~/.config/claude-magic-link
81
+ cp config.example.toml ~/.config/claude-magic-link/config.toml
82
+
83
+ # Edit the config
84
+ $EDITOR ~/Library/Application\ Support/claude-magic-link/config.toml # macOS
85
+ $EDITOR ~/.config/claude-magic-link/config.toml # Linux
86
+ ```
87
+
88
+ Set your IMAP password as an environment variable:
89
+
90
+ ```bash
91
+ export CLAUDE_MAGIC_LINK_PASSWORD="your-imap-password"
92
+ ```
93
+
94
+ ### Run
95
+
96
+ ```bash
97
+ # Test run (logs what would happen, opens nothing)
98
+ claude-magic-link --once --dry-run
99
+
100
+ # Single scan
101
+ claude-magic-link --once
102
+
103
+ # Continuous watching (daemon mode)
104
+ claude-magic-link
105
+
106
+ # With custom config location
107
+ claude-magic-link --config /path/to/config.toml
108
+ ```
109
+
110
+ ## Configuration
111
+
112
+ See [`config.example.toml`](config.example.toml) for the full reference. Key sections:
113
+
114
+ ### IMAP Server
115
+
116
+ ```toml
117
+ [imap]
118
+ host = "imap.example.com"
119
+ port = 993
120
+ ```
121
+
122
+ ### Accounts
123
+
124
+ ```toml
125
+ [[accounts]]
126
+ email = "you@example.com"
127
+ password_env = "CLAUDE_MAGIC_LINK_PASSWORD"
128
+ browser = "default"
129
+ ```
130
+
131
+ Each account maps an email address to a browser:
132
+
133
+ | Value | Platform | Effect |
134
+ |-------|----------|--------|
135
+ | `"default"` | Both | System default browser |
136
+ | `"com.google.Chrome"` | macOS | Chrome via bundle ID |
137
+ | `"com.microsoft.edgemac"` | macOS | Edge via bundle ID |
138
+ | `"org.mozilla.firefox"` | macOS | Firefox via bundle ID |
139
+ | `"google-chrome"` | Linux | Chrome via command |
140
+ | `"firefox"` | Linux | Firefox via command |
141
+
142
+ ### DMARC/DKIM Verification
143
+
144
+ For maximum security, configure your mail provider's authserv-id:
145
+
146
+ ```toml
147
+ [security]
148
+ trusted_authserv_suffix = "mailhosting.your-provider.com"
149
+ ```
150
+
151
+ This verifies that incoming Anthropic mails actually passed DMARC/DKIM at your provider's mail server, preventing spoofed sender addresses. Without this, the tool still checks sender domain, link host/path, and recipient matching.
152
+
153
+ To find your provider's authserv-id, check the `Authentication-Results` header of any email in your inbox. The first field before the semicolon is the authserv-id.
154
+
155
+ ## How It Works
156
+
157
+ ```
158
+ ┌─────────────┐ IMAP IDLE ┌──────────────────┐
159
+ │ Mail Server │ ──── push ─────> │ claude-magic-link │
160
+ │ (Dovecot, │ │ │
161
+ │ Exchange, │ │ 1. Extract link │
162
+ │ Gmail...) │ │ 2. Verify sender │
163
+ │ │ │ 3. Check DMARC │
164
+ └─────────────┘ │ 4. Match To addr │
165
+ │ 5. Dedup check │
166
+ │ 6. Open browser │
167
+ └───────┬──────────┘
168
+
169
+ open -b / xdg-open
170
+
171
+
172
+ ┌──────────────┐
173
+ │ Browser │
174
+ │ (you confirm) │
175
+ └──────────────┘
176
+ ```
177
+
178
+ ## Running as a Service
179
+
180
+ ### macOS (LaunchAgent)
181
+
182
+ Create `~/Library/LaunchAgents/com.user.claude-magic-link.plist`:
183
+
184
+ ```xml
185
+ <?xml version="1.0" encoding="UTF-8"?>
186
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
187
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
188
+ <plist version="1.0">
189
+ <dict>
190
+ <key>Label</key>
191
+ <string>com.user.claude-magic-link</string>
192
+ <key>ProgramArguments</key>
193
+ <array>
194
+ <string>/path/to/claude-magic-link</string>
195
+ </array>
196
+ <!-- Use a wrapper script loading from macOS Keychain for production -->
197
+ <key>EnvironmentVariables</key>
198
+ <dict>
199
+ <key>CLAUDE_MAGIC_LINK_PASSWORD</key>
200
+ <string>your-password</string>
201
+ </dict>
202
+ <key>RunAtLoad</key>
203
+ <true/>
204
+ <key>KeepAlive</key>
205
+ <true/>
206
+ <key>StandardOutPath</key>
207
+ <string>/tmp/claude-magic-link.stdout.log</string>
208
+ <key>StandardErrorPath</key>
209
+ <string>/tmp/claude-magic-link.stderr.log</string>
210
+ </dict>
211
+ </plist>
212
+ ```
213
+
214
+ ```bash
215
+ # macOS 13+:
216
+ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.user.claude-magic-link.plist
217
+ # Older versions:
218
+ launchctl load ~/Library/LaunchAgents/com.user.claude-magic-link.plist
219
+ ```
220
+
221
+ ### Linux (systemd)
222
+
223
+ Create `~/.config/systemd/user/claude-magic-link.service`:
224
+
225
+ ```ini
226
+ [Unit]
227
+ Description=Claude Magic Link Watcher
228
+ After=network-online.target
229
+
230
+ [Service]
231
+ ExecStart=/path/to/claude-magic-link
232
+ # For production, load the password from a credential store instead of plaintext
233
+ Environment=CLAUDE_MAGIC_LINK_PASSWORD=your-password
234
+ Restart=always
235
+ RestartSec=10
236
+
237
+ [Install]
238
+ WantedBy=default.target
239
+ ```
240
+
241
+ ```bash
242
+ systemctl --user enable --now claude-magic-link
243
+ ```
244
+
245
+ ## CLI Reference
246
+
247
+ ```
248
+ usage: claude-magic-link [-h] [--config PATH] [--once] [--dry-run]
249
+ [--max-age N] [--verbose] [--version]
250
+
251
+ options:
252
+ --config, -c PATH Path to config.toml
253
+ --once Single scan instead of continuous watching
254
+ --dry-run Log what would happen without opening anything
255
+ --max-age N Only process mails younger than N minutes
256
+ --verbose, -v Enable debug logging
257
+ --version Show version and exit
258
+ ```
259
+
260
+ ## Security
261
+
262
+ See [SECURITY.md](SECURITY.md) for the full security model. In short, every mail must pass 6 independent checks before a link is opened:
263
+
264
+ 1. Sender domain is `mail.anthropic.com`
265
+ 2. Link uses HTTPS
266
+ 3. Link host is exactly `claude.ai`
267
+ 4. Link path is `/magic-link`
268
+ 5. To-header matches the address encoded in the link
269
+ 6. DMARC/DKIM passes at your provider (when configured)
270
+
271
+ ## Design Decisions
272
+
273
+ **Why IMAP IDLE instead of EWS/Graph API?** IMAP IDLE is provider-agnostic -- it works with Gmail, Dovecot, Exchange, Fastmail, and any standards-compliant server. EWS is Exchange-only and deprecated by Microsoft. Graph API requires Azure AD app registration. IMAP IDLE is the simplest path that works everywhere.
274
+
275
+ **Why Python 3.14?** The IMAP IDLE API (`IMAP4.idle()`) was added in Python 3.14. It handles the protocol correctly (DONE, tags, unsolicited responses) without third-party libraries. This keeps the project dependency-free.
276
+
277
+ **Why no auto-confirm?** Auto-confirming would make this tool a complete authentication bypass, which is a security risk. The manual confirmation step ensures a human is present.
278
+
279
+ **Why `EXAMINE` instead of `SELECT`?** The tool uses `select(readonly=True)`, which sends the IMAP `EXAMINE` command. This guarantees the mailbox is never modified -- no flags are set, no messages moved or deleted.
280
+
281
+ ## FAQ
282
+
283
+ **Why not use the API instead?**
284
+ Claude.ai uses magic-link authentication for the web interface, not the API. The API uses API keys. This tool is for the web/desktop app login flow.
285
+
286
+ **Does it work with Gmail?**
287
+ Yes, if you enable IMAP access and use an app-specific password. Gmail supports IMAP IDLE.
288
+
289
+ **Does it work with OAuth/XOAUTH2?**
290
+ Not yet. The current version uses plain IMAP LOGIN. OAuth support would be a welcome contribution.
291
+
292
+ **Why not just stay logged in?**
293
+ Sessions expire, and with several accounts spread across browsers, profiles, and devices you end up logging in regularly anyway. This tool removes the inbox round-trip from that flow -- only the deliberate confirmation click remains.
294
+
295
+ **Does it work on Windows?**
296
+ Not yet -- the single-instance lock uses `fcntl` and browser launching uses `open`/`xdg-open`. Contributions welcome.
297
+
298
+ ## Development
299
+
300
+ ```bash
301
+ git clone https://github.com/fidpa/claude-magic-link.git
302
+ cd claude-magic-link
303
+ python3.14 -m venv .venv
304
+ .venv/bin/pip install pytest ruff
305
+
306
+ .venv/bin/pytest # run the test suite
307
+ .venv/bin/ruff check . # lint
308
+ ```
309
+
310
+ The test suite covers the security checks (including the attack cases from [SECURITY.md](SECURITY.md)), the IMAP helpers, config loading, and state persistence -- all without a network connection.
311
+
312
+ ## License
313
+
314
+ [MIT](LICENSE)
315
+
316
+ (c) 2026 [Marc Allgeier](https://github.com/fidpa)
317
+
318
+ ## Author
319
+
320
+ Marc Allgeier ([@fidpa](https://github.com/fidpa))
321
+
322
+ **Why I Built This**: I manage multiple Claude.ai accounts for work and personal use. The login flow -- open email, find the link, click it, wait for the browser, confirm -- is a minor friction that adds up fast. This tool eliminates everything except the final confirmation click, which intentionally remains as a security boundary.
323
+
324
+ ## See Also
325
+
326
+ - [cc-telegram-bot](https://github.com/fidpa/cc-telegram-bot) -- Security-hardened Telegram bot for remote Claude Code access (24 security layers)
327
+ - [lydia-bible-bot](https://github.com/fidpa/lydia-bible-bot) -- AI Bible study assistant for Telegram groups
328
+ - [ubuntu-server-security](https://github.com/fidpa/ubuntu-server-security) -- Server hardening (14 components, CIS Benchmark)
329
+ - [bash-production-toolkit](https://github.com/fidpa/bash-production-toolkit) -- Production-ready Bash libraries