otpilot 2.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.
- otpilot-2.0.0/LICENSE +21 -0
- otpilot-2.0.0/PKG-INFO +245 -0
- otpilot-2.0.0/docs/README.md +179 -0
- otpilot-2.0.0/otpilot/__init__.py +4 -0
- otpilot-2.0.0/otpilot/clipboard.py +38 -0
- otpilot-2.0.0/otpilot/config.py +111 -0
- otpilot-2.0.0/otpilot/credentials.py +53 -0
- otpilot-2.0.0/otpilot/gmail_client.py +276 -0
- otpilot-2.0.0/otpilot/hotkey_listener.py +220 -0
- otpilot-2.0.0/otpilot/main.py +234 -0
- otpilot-2.0.0/otpilot/notifier.py +64 -0
- otpilot-2.0.0/otpilot/otp_extractor.py +111 -0
- otpilot-2.0.0/otpilot/setup_wizard.py +249 -0
- otpilot-2.0.0/otpilot/tray.py +165 -0
- otpilot-2.0.0/otpilot.egg-info/PKG-INFO +245 -0
- otpilot-2.0.0/otpilot.egg-info/SOURCES.txt +21 -0
- otpilot-2.0.0/otpilot.egg-info/dependency_links.txt +1 -0
- otpilot-2.0.0/otpilot.egg-info/entry_points.txt +2 -0
- otpilot-2.0.0/otpilot.egg-info/requires.txt +15 -0
- otpilot-2.0.0/otpilot.egg-info/top_level.txt +1 -0
- otpilot-2.0.0/pyproject.toml +63 -0
- otpilot-2.0.0/setup.cfg +4 -0
- otpilot-2.0.0/setup.py +59 -0
otpilot-2.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OTPilot 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.
|
otpilot-2.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: otpilot
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Background CLI utility that copies OTPs from Gmail to clipboard on hotkey trigger
|
|
5
|
+
Author: Jenil
|
|
6
|
+
Author-email: Jenil <mail2jenil.pokar19@gmail.com>
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2026 OTPilot Contributors
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
|
|
29
|
+
Project-URL: Homepage, https://github.com/CodeWithJenil/otpilot
|
|
30
|
+
Project-URL: Documentation, https://github.com/CodeWithJenil/otpilot/tree/main/docs
|
|
31
|
+
Project-URL: Repository, https://github.com/CodeWithJenil/otpilot
|
|
32
|
+
Project-URL: Issues, https://github.com/CodeWithJenil/otpilot/issues
|
|
33
|
+
Keywords: otp,gmail,clipboard,hotkey,automation,2fa
|
|
34
|
+
Classifier: Development Status :: 4 - Beta
|
|
35
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Operating System :: OS Independent
|
|
38
|
+
Classifier: Programming Language :: Python :: 3
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
44
|
+
Classifier: Topic :: Communications :: Email
|
|
45
|
+
Classifier: Topic :: Utilities
|
|
46
|
+
Requires-Python: >=3.8
|
|
47
|
+
Description-Content-Type: text/markdown
|
|
48
|
+
License-File: LICENSE
|
|
49
|
+
Requires-Dist: google-api-python-client>=2.100.0
|
|
50
|
+
Requires-Dist: google-auth-oauthlib>=1.1.0
|
|
51
|
+
Requires-Dist: google-auth>=2.23.0
|
|
52
|
+
Requires-Dist: pynput>=1.7.6
|
|
53
|
+
Requires-Dist: pystray>=0.19.5
|
|
54
|
+
Requires-Dist: Pillow>=10.0.0
|
|
55
|
+
Requires-Dist: pyperclip>=1.8.2
|
|
56
|
+
Requires-Dist: plyer>=2.1.0
|
|
57
|
+
Requires-Dist: click>=8.1.0
|
|
58
|
+
Requires-Dist: rich>=13.0.0
|
|
59
|
+
Provides-Extra: dev
|
|
60
|
+
Requires-Dist: pytest; extra == "dev"
|
|
61
|
+
Requires-Dist: build; extra == "dev"
|
|
62
|
+
Requires-Dist: twine; extra == "dev"
|
|
63
|
+
Dynamic: author
|
|
64
|
+
Dynamic: license-file
|
|
65
|
+
Dynamic: requires-python
|
|
66
|
+
|
|
67
|
+

|
|
68
|
+
|
|
69
|
+
# OTPilot
|
|
70
|
+
|
|
71
|
+
> Press a hotkey. Your OTP is already copied.
|
|
72
|
+
```bash
|
|
73
|
+
pip install otpilot
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
<p align="center">
|
|
79
|
+
<strong>✈ OTPilot v2.0</strong>
|
|
80
|
+
</p>
|
|
81
|
+
|
|
82
|
+
<p align="center">
|
|
83
|
+
<em>The new era of private OTP fetching. Background utility that copies OTPs from Gmail to your clipboard with a single hotkey.</em>
|
|
84
|
+
</p>
|
|
85
|
+
|
|
86
|
+
<p align="center">
|
|
87
|
+
<a href="https://github.com/codewithjenil/otpilot">GitHub</a> •
|
|
88
|
+
<a href="#installation">Install</a> •
|
|
89
|
+
<a href="#quickstart">Quickstart</a> •
|
|
90
|
+
<a href="SETUP.md">OAuth Setup</a> •
|
|
91
|
+
<a href="CONTRIBUTING.md">Contributing</a>
|
|
92
|
+
</p>
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## How It Works
|
|
97
|
+
|
|
98
|
+
1. **OTPilot sits silently in your system tray** — no windows, no polling, zero CPU usage.
|
|
99
|
+
2. **Press your hotkey** (e.g. `Ctrl+Shift+O`) — OTPilot fetches your last 10 emails from Gmail and extracts the OTP.
|
|
100
|
+
3. **OTP is copied to your clipboard** — just paste it. A desktop notification confirms the action.
|
|
101
|
+
|
|
102
|
+
## Installation
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
pip install otpilot
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Requirements
|
|
109
|
+
|
|
110
|
+
- **Python 3.8+**
|
|
111
|
+
- **Gmail account**
|
|
112
|
+
- **Google Cloud project** with Gmail API enabled (free — [setup guide](SETUP.md))
|
|
113
|
+
- **OS**: macOS, Windows, or Linux
|
|
114
|
+
|
|
115
|
+
> **Linux users**: Make sure `xclip` or `xsel` is installed for clipboard support.
|
|
116
|
+
> ```bash
|
|
117
|
+
> sudo apt install xclip # Debian/Ubuntu
|
|
118
|
+
> ```
|
|
119
|
+
|
|
120
|
+
## Quickstart
|
|
121
|
+
|
|
122
|
+
### 1. Get Your Credentials
|
|
123
|
+
|
|
124
|
+
Follow the steps above (or the [full guide](SETUP.md)) to download your `credentials.json` file.
|
|
125
|
+
|
|
126
|
+
### 2. Run Setup
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
otpilot setup
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The wizard will:
|
|
133
|
+
- Ask for the path to your `credentials.json` file
|
|
134
|
+
- Open your browser for Google sign-in (one-time authorization)
|
|
135
|
+
- Let you set your preferred hotkey
|
|
136
|
+
- Save your configuration
|
|
137
|
+
|
|
138
|
+
### 3. Start OTPilot
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
otpilot start
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
OTPilot runs in the background with a system tray icon. Press your hotkey whenever you need an OTP.
|
|
145
|
+
|
|
146
|
+
### 4. Daily Use
|
|
147
|
+
|
|
148
|
+
1. Receive an OTP email in Gmail
|
|
149
|
+
2. Press your hotkey (default: `Ctrl+Shift+O`)
|
|
150
|
+
3. Paste the OTP — done!
|
|
151
|
+
|
|
152
|
+
## Getting Your credentials.json
|
|
153
|
+
|
|
154
|
+
Before using OTPilot, you need to download a `credentials.json` file from Google Cloud Console. This is a one-time, ~5 minute process.
|
|
155
|
+
|
|
156
|
+
1. Go to [console.cloud.google.com](https://console.cloud.google.com)
|
|
157
|
+
2. Create a new project (or use an existing one)
|
|
158
|
+
3. Enable the **Gmail API**
|
|
159
|
+
4. Go to **APIs & Services → Credentials**
|
|
160
|
+
5. Create an **OAuth 2.0 Client ID** (Application type: **Desktop app**)
|
|
161
|
+
6. Click **Download JSON** to save the `credentials.json` file
|
|
162
|
+
|
|
163
|
+
> **Your credentials never leave your machine.** The file is stored locally at `~/.otpilot/credentials.json`.
|
|
164
|
+
|
|
165
|
+
For detailed, step-by-step instructions with screenshots, see [**SETUP.md**](SETUP.md).
|
|
166
|
+
|
|
167
|
+
## CLI Commands
|
|
168
|
+
|
|
169
|
+
| Command | Description |
|
|
170
|
+
| ------------------ | ---------------------------------------- |
|
|
171
|
+
| `otpilot setup` | Run or re-run the interactive setup |
|
|
172
|
+
| `otpilot start` | Start the background service |
|
|
173
|
+
| `otpilot status` | Show auth state, hotkey, and config path |
|
|
174
|
+
| `otpilot version` | Print the version number |
|
|
175
|
+
|
|
176
|
+
## Configuration
|
|
177
|
+
|
|
178
|
+
OTPilot stores its configuration at `~/.otpilot/config.json`:
|
|
179
|
+
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"hotkey": "ctrl+shift+o",
|
|
183
|
+
"notify_on_copy": true,
|
|
184
|
+
"otp_max_age_minutes": 10,
|
|
185
|
+
"email_fetch_count": 10
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
| Field | Type | Default | Description |
|
|
190
|
+
| --------------------- | ------ | ---------------- | ---------------------------------------------- |
|
|
191
|
+
| `hotkey` | string | `ctrl+shift+o` | Global hotkey combination |
|
|
192
|
+
| `notify_on_copy` | bool | `true` | Show desktop notification when OTP is copied |
|
|
193
|
+
| `otp_max_age_minutes` | int | `10` | Ignore emails older than this (minutes) |
|
|
194
|
+
| `email_fetch_count` | int | `10` | Number of recent emails to scan |
|
|
195
|
+
|
|
196
|
+
### Files Stored Locally
|
|
197
|
+
|
|
198
|
+
| File | Purpose |
|
|
199
|
+
| -------------------------------- | ------------------------------------ |
|
|
200
|
+
| `~/.otpilot/config.json` | Your hotkey and settings |
|
|
201
|
+
| `~/.otpilot/credentials.json` | Your Google OAuth credentials |
|
|
202
|
+
| `~/.otpilot/token.json` | OAuth session token (auto-generated) |
|
|
203
|
+
|
|
204
|
+
## Platform Support
|
|
205
|
+
|
|
206
|
+
| Platform | Status | Notes |
|
|
207
|
+
| -------- | ------ | -------------------------------------- |
|
|
208
|
+
| macOS | ✅ | Full support |
|
|
209
|
+
| Windows | ✅ | Full support |
|
|
210
|
+
| Linux | ✅ | Requires `xclip`/`xsel` for clipboard |
|
|
211
|
+
|
|
212
|
+
## How OTP Extraction Works
|
|
213
|
+
|
|
214
|
+
OTPilot scans the subject line and body of your recent emails for:
|
|
215
|
+
|
|
216
|
+
- **4–8 digit standalone numbers** near context words like *OTP*, *code*, *verify*, *verification*, *one-time*, *passcode*, *authentication*
|
|
217
|
+
- Only emails within the configured time window are considered
|
|
218
|
+
- If multiple OTPs are found, the most recent one wins
|
|
219
|
+
|
|
220
|
+
## Security & Privacy
|
|
221
|
+
|
|
222
|
+
- **Your credentials stay local**: `credentials.json` is stored on your machine at `~/.otpilot/` and is never uploaded anywhere.
|
|
223
|
+
- **Read-only access**: OTPilot only reads your emails — it cannot send, delete, or modify anything.
|
|
224
|
+
- **Local storage only**: Your OAuth token is stored locally at `~/.otpilot/token.json`. Nothing is sent to any third-party server.
|
|
225
|
+
- **On-demand only**: Emails are fetched only when you press the hotkey. There is no background polling.
|
|
226
|
+
|
|
227
|
+
## Troubleshooting
|
|
228
|
+
|
|
229
|
+
| Issue | Solution |
|
|
230
|
+
| --------------------------------- | -------------------------------------------------------------- |
|
|
231
|
+
| "credentials.json not found" | Run `otpilot setup` and provide your credentials file |
|
|
232
|
+
| "Invalid credentials file" | Re-download credentials.json from Google Cloud Console |
|
|
233
|
+
| "Not authenticated" error | Run `otpilot setup` to re-authenticate |
|
|
234
|
+
| No OTP found | Check `otp_max_age_minutes` — the email might be too old |
|
|
235
|
+
| Clipboard not working (Linux) | Install `xclip`: `sudo apt install xclip` |
|
|
236
|
+
| Hotkey not working | Run `otpilot setup` to reconfigure the hotkey |
|
|
237
|
+
| Tray icon not visible | Check your system tray / menu bar settings |
|
|
238
|
+
|
|
239
|
+
## Contributing
|
|
240
|
+
|
|
241
|
+
Contributions are welcome! See [**CONTRIBUTING.md**](CONTRIBUTING.md) for guidelines on setting up the development environment, running tests, and submitting pull requests.
|
|
242
|
+
|
|
243
|
+
## License
|
|
244
|
+
|
|
245
|
+
[MIT](../LICENSE) — use it freely.
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
3
|
+
# OTPilot
|
|
4
|
+
|
|
5
|
+
> Press a hotkey. Your OTP is already copied.
|
|
6
|
+
```bash
|
|
7
|
+
pip install otpilot
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<strong>✈ OTPilot v2.0</strong>
|
|
14
|
+
</p>
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
17
|
+
<em>The new era of private OTP fetching. Background utility that copies OTPs from Gmail to your clipboard with a single hotkey.</em>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<a href="https://github.com/codewithjenil/otpilot">GitHub</a> •
|
|
22
|
+
<a href="#installation">Install</a> •
|
|
23
|
+
<a href="#quickstart">Quickstart</a> •
|
|
24
|
+
<a href="SETUP.md">OAuth Setup</a> •
|
|
25
|
+
<a href="CONTRIBUTING.md">Contributing</a>
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## How It Works
|
|
31
|
+
|
|
32
|
+
1. **OTPilot sits silently in your system tray** — no windows, no polling, zero CPU usage.
|
|
33
|
+
2. **Press your hotkey** (e.g. `Ctrl+Shift+O`) — OTPilot fetches your last 10 emails from Gmail and extracts the OTP.
|
|
34
|
+
3. **OTP is copied to your clipboard** — just paste it. A desktop notification confirms the action.
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install otpilot
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Requirements
|
|
43
|
+
|
|
44
|
+
- **Python 3.8+**
|
|
45
|
+
- **Gmail account**
|
|
46
|
+
- **Google Cloud project** with Gmail API enabled (free — [setup guide](SETUP.md))
|
|
47
|
+
- **OS**: macOS, Windows, or Linux
|
|
48
|
+
|
|
49
|
+
> **Linux users**: Make sure `xclip` or `xsel` is installed for clipboard support.
|
|
50
|
+
> ```bash
|
|
51
|
+
> sudo apt install xclip # Debian/Ubuntu
|
|
52
|
+
> ```
|
|
53
|
+
|
|
54
|
+
## Quickstart
|
|
55
|
+
|
|
56
|
+
### 1. Get Your Credentials
|
|
57
|
+
|
|
58
|
+
Follow the steps above (or the [full guide](SETUP.md)) to download your `credentials.json` file.
|
|
59
|
+
|
|
60
|
+
### 2. Run Setup
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
otpilot setup
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The wizard will:
|
|
67
|
+
- Ask for the path to your `credentials.json` file
|
|
68
|
+
- Open your browser for Google sign-in (one-time authorization)
|
|
69
|
+
- Let you set your preferred hotkey
|
|
70
|
+
- Save your configuration
|
|
71
|
+
|
|
72
|
+
### 3. Start OTPilot
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
otpilot start
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
OTPilot runs in the background with a system tray icon. Press your hotkey whenever you need an OTP.
|
|
79
|
+
|
|
80
|
+
### 4. Daily Use
|
|
81
|
+
|
|
82
|
+
1. Receive an OTP email in Gmail
|
|
83
|
+
2. Press your hotkey (default: `Ctrl+Shift+O`)
|
|
84
|
+
3. Paste the OTP — done!
|
|
85
|
+
|
|
86
|
+
## Getting Your credentials.json
|
|
87
|
+
|
|
88
|
+
Before using OTPilot, you need to download a `credentials.json` file from Google Cloud Console. This is a one-time, ~5 minute process.
|
|
89
|
+
|
|
90
|
+
1. Go to [console.cloud.google.com](https://console.cloud.google.com)
|
|
91
|
+
2. Create a new project (or use an existing one)
|
|
92
|
+
3. Enable the **Gmail API**
|
|
93
|
+
4. Go to **APIs & Services → Credentials**
|
|
94
|
+
5. Create an **OAuth 2.0 Client ID** (Application type: **Desktop app**)
|
|
95
|
+
6. Click **Download JSON** to save the `credentials.json` file
|
|
96
|
+
|
|
97
|
+
> **Your credentials never leave your machine.** The file is stored locally at `~/.otpilot/credentials.json`.
|
|
98
|
+
|
|
99
|
+
For detailed, step-by-step instructions with screenshots, see [**SETUP.md**](SETUP.md).
|
|
100
|
+
|
|
101
|
+
## CLI Commands
|
|
102
|
+
|
|
103
|
+
| Command | Description |
|
|
104
|
+
| ------------------ | ---------------------------------------- |
|
|
105
|
+
| `otpilot setup` | Run or re-run the interactive setup |
|
|
106
|
+
| `otpilot start` | Start the background service |
|
|
107
|
+
| `otpilot status` | Show auth state, hotkey, and config path |
|
|
108
|
+
| `otpilot version` | Print the version number |
|
|
109
|
+
|
|
110
|
+
## Configuration
|
|
111
|
+
|
|
112
|
+
OTPilot stores its configuration at `~/.otpilot/config.json`:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"hotkey": "ctrl+shift+o",
|
|
117
|
+
"notify_on_copy": true,
|
|
118
|
+
"otp_max_age_minutes": 10,
|
|
119
|
+
"email_fetch_count": 10
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
| Field | Type | Default | Description |
|
|
124
|
+
| --------------------- | ------ | ---------------- | ---------------------------------------------- |
|
|
125
|
+
| `hotkey` | string | `ctrl+shift+o` | Global hotkey combination |
|
|
126
|
+
| `notify_on_copy` | bool | `true` | Show desktop notification when OTP is copied |
|
|
127
|
+
| `otp_max_age_minutes` | int | `10` | Ignore emails older than this (minutes) |
|
|
128
|
+
| `email_fetch_count` | int | `10` | Number of recent emails to scan |
|
|
129
|
+
|
|
130
|
+
### Files Stored Locally
|
|
131
|
+
|
|
132
|
+
| File | Purpose |
|
|
133
|
+
| -------------------------------- | ------------------------------------ |
|
|
134
|
+
| `~/.otpilot/config.json` | Your hotkey and settings |
|
|
135
|
+
| `~/.otpilot/credentials.json` | Your Google OAuth credentials |
|
|
136
|
+
| `~/.otpilot/token.json` | OAuth session token (auto-generated) |
|
|
137
|
+
|
|
138
|
+
## Platform Support
|
|
139
|
+
|
|
140
|
+
| Platform | Status | Notes |
|
|
141
|
+
| -------- | ------ | -------------------------------------- |
|
|
142
|
+
| macOS | ✅ | Full support |
|
|
143
|
+
| Windows | ✅ | Full support |
|
|
144
|
+
| Linux | ✅ | Requires `xclip`/`xsel` for clipboard |
|
|
145
|
+
|
|
146
|
+
## How OTP Extraction Works
|
|
147
|
+
|
|
148
|
+
OTPilot scans the subject line and body of your recent emails for:
|
|
149
|
+
|
|
150
|
+
- **4–8 digit standalone numbers** near context words like *OTP*, *code*, *verify*, *verification*, *one-time*, *passcode*, *authentication*
|
|
151
|
+
- Only emails within the configured time window are considered
|
|
152
|
+
- If multiple OTPs are found, the most recent one wins
|
|
153
|
+
|
|
154
|
+
## Security & Privacy
|
|
155
|
+
|
|
156
|
+
- **Your credentials stay local**: `credentials.json` is stored on your machine at `~/.otpilot/` and is never uploaded anywhere.
|
|
157
|
+
- **Read-only access**: OTPilot only reads your emails — it cannot send, delete, or modify anything.
|
|
158
|
+
- **Local storage only**: Your OAuth token is stored locally at `~/.otpilot/token.json`. Nothing is sent to any third-party server.
|
|
159
|
+
- **On-demand only**: Emails are fetched only when you press the hotkey. There is no background polling.
|
|
160
|
+
|
|
161
|
+
## Troubleshooting
|
|
162
|
+
|
|
163
|
+
| Issue | Solution |
|
|
164
|
+
| --------------------------------- | -------------------------------------------------------------- |
|
|
165
|
+
| "credentials.json not found" | Run `otpilot setup` and provide your credentials file |
|
|
166
|
+
| "Invalid credentials file" | Re-download credentials.json from Google Cloud Console |
|
|
167
|
+
| "Not authenticated" error | Run `otpilot setup` to re-authenticate |
|
|
168
|
+
| No OTP found | Check `otp_max_age_minutes` — the email might be too old |
|
|
169
|
+
| Clipboard not working (Linux) | Install `xclip`: `sudo apt install xclip` |
|
|
170
|
+
| Hotkey not working | Run `otpilot setup` to reconfigure the hotkey |
|
|
171
|
+
| Tray icon not visible | Check your system tray / menu bar settings |
|
|
172
|
+
|
|
173
|
+
## Contributing
|
|
174
|
+
|
|
175
|
+
Contributions are welcome! See [**CONTRIBUTING.md**](CONTRIBUTING.md) for guidelines on setting up the development environment, running tests, and submitting pull requests.
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
[MIT](../LICENSE) — use it freely.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Clipboard operations for OTPilot.
|
|
2
|
+
|
|
3
|
+
Wraps ``pyperclip`` with error handling so clipboard failures never crash
|
|
4
|
+
the application.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pyperclip
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ClipboardError(Exception):
|
|
11
|
+
"""Raised when a clipboard operation fails."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, message: str = "Could not access the clipboard.") -> None:
|
|
14
|
+
super().__init__(message)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def copy_to_clipboard(text: str) -> None:
|
|
18
|
+
"""Copy text to the system clipboard.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
text: The string to copy.
|
|
22
|
+
|
|
23
|
+
Raises:
|
|
24
|
+
ClipboardError: If the clipboard is unavailable (e.g. no display
|
|
25
|
+
server on Linux, or missing clipboard utilities).
|
|
26
|
+
"""
|
|
27
|
+
try:
|
|
28
|
+
pyperclip.copy(text)
|
|
29
|
+
except pyperclip.PyperclipException as exc:
|
|
30
|
+
raise ClipboardError(
|
|
31
|
+
"Could not copy to clipboard. "
|
|
32
|
+
"On Linux, make sure xclip or xsel is installed. "
|
|
33
|
+
f"Details: {exc}"
|
|
34
|
+
) from exc
|
|
35
|
+
except Exception as exc:
|
|
36
|
+
raise ClipboardError(
|
|
37
|
+
f"Unexpected clipboard error: {exc}"
|
|
38
|
+
) from exc
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Configuration management for OTPilot.
|
|
2
|
+
|
|
3
|
+
Handles reading and writing of ~/.otpilot/config.json.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, Optional
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Default configuration directory and file paths
|
|
12
|
+
CONFIG_DIR: Path = Path.home() / ".otpilot"
|
|
13
|
+
CONFIG_FILE: Path = CONFIG_DIR / "config.json"
|
|
14
|
+
TOKEN_FILE: Path = CONFIG_DIR / "token.json"
|
|
15
|
+
|
|
16
|
+
# Default configuration values
|
|
17
|
+
DEFAULT_CONFIG: Dict[str, Any] = {
|
|
18
|
+
"hotkey": "ctrl+shift+o",
|
|
19
|
+
"notify_on_copy": True,
|
|
20
|
+
"otp_max_age_minutes": 10,
|
|
21
|
+
"email_fetch_count": 10,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _ensure_config_dir() -> None:
|
|
26
|
+
"""Create the configuration directory if it doesn't exist."""
|
|
27
|
+
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_config() -> Dict[str, Any]:
|
|
31
|
+
"""Load and return the current configuration.
|
|
32
|
+
|
|
33
|
+
If the config file doesn't exist, creates it with default values.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Dictionary containing all configuration values.
|
|
37
|
+
"""
|
|
38
|
+
_ensure_config_dir()
|
|
39
|
+
|
|
40
|
+
if not CONFIG_FILE.exists():
|
|
41
|
+
save_config(DEFAULT_CONFIG)
|
|
42
|
+
return DEFAULT_CONFIG.copy()
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
|
|
46
|
+
stored_config: Dict[str, Any] = json.load(f)
|
|
47
|
+
except (json.JSONDecodeError, OSError):
|
|
48
|
+
# If config is corrupted, reset to defaults
|
|
49
|
+
save_config(DEFAULT_CONFIG)
|
|
50
|
+
return DEFAULT_CONFIG.copy()
|
|
51
|
+
|
|
52
|
+
# Merge with defaults so new keys are always present
|
|
53
|
+
merged = DEFAULT_CONFIG.copy()
|
|
54
|
+
merged.update(stored_config)
|
|
55
|
+
return merged
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def save_config(data: Dict[str, Any]) -> None:
|
|
59
|
+
"""Save configuration data to disk.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
data: Dictionary of configuration values to persist.
|
|
63
|
+
"""
|
|
64
|
+
_ensure_config_dir()
|
|
65
|
+
|
|
66
|
+
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
|
|
67
|
+
json.dump(data, f, indent=2, sort_keys=True)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def config_exists() -> bool:
|
|
71
|
+
"""Check whether a configuration file already exists.
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
True if config.json is present, False otherwise.
|
|
75
|
+
"""
|
|
76
|
+
return CONFIG_FILE.exists()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def token_exists() -> bool:
|
|
80
|
+
"""Check whether an OAuth token file already exists.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
True if token.json is present, False otherwise.
|
|
84
|
+
"""
|
|
85
|
+
return TOKEN_FILE.exists()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def get_value(key: str, default: Optional[Any] = None) -> Any:
|
|
89
|
+
"""Retrieve a single configuration value.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
key: The config key to look up.
|
|
93
|
+
default: Fallback value if the key is missing.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
The configuration value, or *default* if not found.
|
|
97
|
+
"""
|
|
98
|
+
config = get_config()
|
|
99
|
+
return config.get(key, default)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def set_value(key: str, value: Any) -> None:
|
|
103
|
+
"""Update a single configuration value and persist to disk.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
key: The config key to update.
|
|
107
|
+
value: The new value.
|
|
108
|
+
"""
|
|
109
|
+
config = get_config()
|
|
110
|
+
config[key] = value
|
|
111
|
+
save_config(config)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""OAuth2 credential paths and scopes for OTPilot.
|
|
2
|
+
|
|
3
|
+
Users provide their own ``credentials.json`` downloaded from the
|
|
4
|
+
Google Cloud Console. The file is stored at ``~/.otpilot/credentials.json``
|
|
5
|
+
and never leaves the user's machine.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# OAuth2 configuration
|
|
12
|
+
SCOPES: list = ["https://www.googleapis.com/auth/gmail.readonly"]
|
|
13
|
+
|
|
14
|
+
# Paths
|
|
15
|
+
CREDENTIALS_FILE: Path = Path.home() / ".otpilot" / "credentials.json"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def credentials_exist() -> bool:
|
|
19
|
+
"""Check whether a user-provided credentials.json file exists.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
True if ``~/.otpilot/credentials.json`` is present, False otherwise.
|
|
23
|
+
"""
|
|
24
|
+
return CREDENTIALS_FILE.exists()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def validate_credentials_file(path: Path) -> bool:
|
|
28
|
+
"""Validate that a file looks like a Google OAuth2 credentials JSON.
|
|
29
|
+
|
|
30
|
+
Performs a basic structural check — verifies the file is valid JSON
|
|
31
|
+
and contains either an ``installed`` or ``web`` top-level key with
|
|
32
|
+
a ``client_id`` field.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
path: Path to the credentials JSON file.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
True if the file appears valid, False otherwise.
|
|
39
|
+
"""
|
|
40
|
+
import json
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
44
|
+
data = json.load(f)
|
|
45
|
+
except (json.JSONDecodeError, OSError):
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
# Google credentials JSON has either "installed" or "web" as a top key
|
|
49
|
+
for key in ("installed", "web"):
|
|
50
|
+
if key in data and "client_id" in data[key]:
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
return False
|