otpilot 2.3.2__tar.gz → 2.3.4__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.3.2/otpilot.egg-info → otpilot-2.3.4}/PKG-INFO +2 -1
- {otpilot-2.3.2 → otpilot-2.3.4}/docs/README.md +1 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/otpilot/__init__.py +1 -1
- {otpilot-2.3.2 → otpilot-2.3.4}/otpilot/config.py +1 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/otpilot/main.py +263 -1
- {otpilot-2.3.2 → otpilot-2.3.4/otpilot.egg-info}/PKG-INFO +2 -1
- {otpilot-2.3.2 → otpilot-2.3.4}/LICENSE +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/README.md +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/otpilot/clipboard.py +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/otpilot/gmail_client.py +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/otpilot/history.py +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/otpilot/hotkey_listener.py +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/otpilot/logger.py +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/otpilot/notifier.py +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/otpilot/otp_extractor.py +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/otpilot/setup_wizard.py +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/otpilot/token_store.py +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/otpilot/tray.py +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/otpilot.egg-info/SOURCES.txt +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/otpilot.egg-info/dependency_links.txt +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/otpilot.egg-info/entry_points.txt +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/otpilot.egg-info/requires.txt +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/otpilot.egg-info/top_level.txt +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/pyproject.toml +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/setup.cfg +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/setup.py +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/tests/test_clipboard.py +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/tests/test_config.py +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/tests/test_gmail_client.py +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/tests/test_history.py +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/tests/test_logger.py +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/tests/test_main.py +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/tests/test_notifier.py +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/tests/test_otp_extractor.py +0 -0
- {otpilot-2.3.2 → otpilot-2.3.4}/tests/test_token_store.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: otpilot
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.4
|
|
4
4
|
Summary: Background CLI utility that copies OTPs from Gmail to clipboard on hotkey trigger
|
|
5
5
|
Author: Jenil
|
|
6
6
|
Author-email: Jenil <mail2jenil.pokar19@gmail.com>
|
|
@@ -261,6 +261,7 @@ Use this when you prefer no OAuth flow inside OTPilot.
|
|
|
261
261
|
| `otpilot history --clear`| Clear OTP history |
|
|
262
262
|
| `otpilot logs` | Tail the OTPilot log file |
|
|
263
263
|
| `otpilot status` | Show auth state, hotkey, and config |
|
|
264
|
+
| `otpilot doctor` | Run diagnostics to validate environment |
|
|
264
265
|
| `otpilot hotkey` | View or reconfigure the global hotkey |
|
|
265
266
|
| `otpilot logout` | Clear stored auth token |
|
|
266
267
|
| `otpilot update` | Check PyPI for updates and upgrade |
|
|
@@ -192,6 +192,7 @@ Use this when you prefer no OAuth flow inside OTPilot.
|
|
|
192
192
|
| `otpilot history --clear`| Clear OTP history |
|
|
193
193
|
| `otpilot logs` | Tail the OTPilot log file |
|
|
194
194
|
| `otpilot status` | Show auth state, hotkey, and config |
|
|
195
|
+
| `otpilot doctor` | Run diagnostics to validate environment |
|
|
195
196
|
| `otpilot hotkey` | View or reconfigure the global hotkey |
|
|
196
197
|
| `otpilot logout` | Clear stored auth token |
|
|
197
198
|
| `otpilot update` | Check PyPI for updates and upgrade |
|
|
@@ -25,6 +25,7 @@ TOKEN_FILE: Path = CONFIG_DIR / "token.json"
|
|
|
25
25
|
# Default configuration values used for first run and missing keys.
|
|
26
26
|
DEFAULT_CONFIG: Dict[str, Any] = {
|
|
27
27
|
"auth_mode": "firebase",
|
|
28
|
+
"firebase_web_url": "https://jenil-otpilot.vercel.app/auth",
|
|
28
29
|
"hotkey": "ctrl+shift+o",
|
|
29
30
|
"notify_on_copy": True,
|
|
30
31
|
"otp_max_age_minutes": 10,
|
|
@@ -11,9 +11,11 @@ Key exports:
|
|
|
11
11
|
main: Console script entry point.
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
+
import imaplib
|
|
14
15
|
import os
|
|
15
16
|
import shutil
|
|
16
17
|
import signal
|
|
18
|
+
import socket
|
|
17
19
|
import subprocess
|
|
18
20
|
import sys
|
|
19
21
|
import threading
|
|
@@ -26,7 +28,7 @@ from rich.console import Console
|
|
|
26
28
|
|
|
27
29
|
from otpilot import __version__
|
|
28
30
|
from otpilot.clipboard import ClipboardError, copy_to_clipboard
|
|
29
|
-
from otpilot.config import config_exists, get_config, token_exists
|
|
31
|
+
from otpilot.config import CONFIG_FILE, config_exists, get_config, get_value, token_exists
|
|
30
32
|
from otpilot.gmail_client import GmailAuthError, NotAuthenticatedError, get_fetch_function
|
|
31
33
|
from otpilot.logger import LOG_FILE, get_logger
|
|
32
34
|
|
|
@@ -517,6 +519,266 @@ def status() -> None:
|
|
|
517
519
|
_print_feedback_prompt()
|
|
518
520
|
|
|
519
521
|
|
|
522
|
+
@cli.command()
|
|
523
|
+
def doctor() -> None:
|
|
524
|
+
"""Run diagnostic checks to validate OTPilot environment health.
|
|
525
|
+
|
|
526
|
+
Returns:
|
|
527
|
+
None: This command does not return a value.
|
|
528
|
+
"""
|
|
529
|
+
from otpilot.token_store import load_app_password, load_token
|
|
530
|
+
|
|
531
|
+
console.print("[bold]OTPilot Doctor[/bold]")
|
|
532
|
+
console.print("Running diagnostics...")
|
|
533
|
+
|
|
534
|
+
passed = 0
|
|
535
|
+
warnings = 0
|
|
536
|
+
failed = 0
|
|
537
|
+
|
|
538
|
+
auth_mode: Optional[str] = None
|
|
539
|
+
gmail_reachable: Optional[bool] = None
|
|
540
|
+
imap_reachable: Optional[bool] = None
|
|
541
|
+
|
|
542
|
+
try:
|
|
543
|
+
version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
544
|
+
if sys.version_info >= (3, 10):
|
|
545
|
+
console.print(f"[green]✓[/green] Python {version} — OK")
|
|
546
|
+
passed += 1
|
|
547
|
+
else:
|
|
548
|
+
console.print(f"[red]✗[/red] Python {version} — OTPilot requires 3.10+")
|
|
549
|
+
failed += 1
|
|
550
|
+
except Exception as exc:
|
|
551
|
+
console.print(f"[red]✗[/red] Python version: unexpected error — {exc}")
|
|
552
|
+
failed += 1
|
|
553
|
+
|
|
554
|
+
try:
|
|
555
|
+
if config_exists():
|
|
556
|
+
console.print(f"[green]✓[/green] Config file found at {CONFIG_FILE}")
|
|
557
|
+
passed += 1
|
|
558
|
+
else:
|
|
559
|
+
console.print("[red]✗[/red] Config file missing — run [bold]otpilot setup[/bold]")
|
|
560
|
+
failed += 1
|
|
561
|
+
except Exception as exc:
|
|
562
|
+
console.print(f"[red]✗[/red] Config file exists: unexpected error — {exc}")
|
|
563
|
+
failed += 1
|
|
564
|
+
|
|
565
|
+
try:
|
|
566
|
+
auth_mode = get_value("auth_mode")
|
|
567
|
+
console.print(f"[green]✓[/green] Auth mode: {auth_mode}")
|
|
568
|
+
passed += 1
|
|
569
|
+
except Exception as exc:
|
|
570
|
+
console.print(f"[red]✗[/red] Auth mode: unexpected error — {exc}")
|
|
571
|
+
failed += 1
|
|
572
|
+
|
|
573
|
+
try:
|
|
574
|
+
if token_exists():
|
|
575
|
+
console.print("[green]✓[/green] Credential stored")
|
|
576
|
+
passed += 1
|
|
577
|
+
else:
|
|
578
|
+
console.print("[red]✗[/red] No credential found — run [bold]otpilot setup[/bold]")
|
|
579
|
+
failed += 1
|
|
580
|
+
except Exception as exc:
|
|
581
|
+
console.print(f"[red]✗[/red] Token stored: unexpected error — {exc}")
|
|
582
|
+
failed += 1
|
|
583
|
+
|
|
584
|
+
if auth_mode in {"firebase", "credentials"}:
|
|
585
|
+
try:
|
|
586
|
+
token_payload = load_token() or {}
|
|
587
|
+
expires_at = token_payload.get("expires_at")
|
|
588
|
+
if not expires_at:
|
|
589
|
+
console.print(
|
|
590
|
+
"[yellow]⚠[/yellow] Token expiry unknown — no expires_at stored"
|
|
591
|
+
)
|
|
592
|
+
warnings += 1
|
|
593
|
+
else:
|
|
594
|
+
now = time.time()
|
|
595
|
+
expires_at_int = int(expires_at)
|
|
596
|
+
delta = expires_at_int - now
|
|
597
|
+
if delta < 0:
|
|
598
|
+
minutes_ago = int((abs(delta) + 59) // 60)
|
|
599
|
+
console.print(
|
|
600
|
+
"[red]✗[/red] Token expired "
|
|
601
|
+
f"{minutes_ago} minutes ago — run [bold]otpilot fetch[/bold] "
|
|
602
|
+
"to trigger silent refresh or [bold]otpilot setup[/bold] to re-authenticate"
|
|
603
|
+
)
|
|
604
|
+
failed += 1
|
|
605
|
+
elif delta <= 5 * 60:
|
|
606
|
+
minutes_left = int((delta + 59) // 60)
|
|
607
|
+
console.print(
|
|
608
|
+
"[yellow]⚠[/yellow] Token expires in "
|
|
609
|
+
f"{minutes_left} minutes — will auto-refresh on next fetch"
|
|
610
|
+
)
|
|
611
|
+
warnings += 1
|
|
612
|
+
else:
|
|
613
|
+
minutes_left = int((delta + 59) // 60)
|
|
614
|
+
console.print(f"[green]✓[/green] Token valid — expires in {minutes_left} minutes")
|
|
615
|
+
passed += 1
|
|
616
|
+
except Exception as exc:
|
|
617
|
+
console.print(f"[red]✗[/red] Token expiry: unexpected error — {exc}")
|
|
618
|
+
failed += 1
|
|
619
|
+
|
|
620
|
+
try:
|
|
621
|
+
token_payload = load_token() or {}
|
|
622
|
+
refresh_token = str(token_payload.get("refresh_token", "")).strip()
|
|
623
|
+
if refresh_token:
|
|
624
|
+
console.print("[green]✓[/green] Refresh token stored — silent renewal enabled")
|
|
625
|
+
passed += 1
|
|
626
|
+
else:
|
|
627
|
+
console.print(
|
|
628
|
+
"[red]✗[/red] No refresh token — token cannot be silently renewed. "
|
|
629
|
+
"Re-run [bold]otpilot setup[/bold] to fix this"
|
|
630
|
+
)
|
|
631
|
+
failed += 1
|
|
632
|
+
except Exception as exc:
|
|
633
|
+
console.print(f"[red]✗[/red] Refresh token present: unexpected error — {exc}")
|
|
634
|
+
failed += 1
|
|
635
|
+
|
|
636
|
+
try:
|
|
637
|
+
requests.get("https://gmail.googleapis.com", timeout=5)
|
|
638
|
+
console.print("[green]✓[/green] Gmail API reachable")
|
|
639
|
+
passed += 1
|
|
640
|
+
gmail_reachable = True
|
|
641
|
+
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
|
|
642
|
+
console.print("[red]✗[/red] Gmail API unreachable — check your internet connection")
|
|
643
|
+
failed += 1
|
|
644
|
+
gmail_reachable = False
|
|
645
|
+
except Exception as exc:
|
|
646
|
+
console.print(f"[red]✗[/red] Gmail API reachability: unexpected error — {exc}")
|
|
647
|
+
failed += 1
|
|
648
|
+
|
|
649
|
+
if auth_mode == "imap":
|
|
650
|
+
host = ""
|
|
651
|
+
port = 993
|
|
652
|
+
try:
|
|
653
|
+
host = str(get_value("imap_host", ""))
|
|
654
|
+
port = int(get_value("imap_port", 993) or 993)
|
|
655
|
+
_ = get_value("imap_user", "")
|
|
656
|
+
previous_timeout = socket.getdefaulttimeout()
|
|
657
|
+
socket.setdefaulttimeout(5)
|
|
658
|
+
client: Optional[imaplib.IMAP4_SSL] = None
|
|
659
|
+
try:
|
|
660
|
+
client = imaplib.IMAP4_SSL(host, port)
|
|
661
|
+
console.print(f"[green]✓[/green] IMAP server {host}:{port} reachable")
|
|
662
|
+
passed += 1
|
|
663
|
+
imap_reachable = True
|
|
664
|
+
finally:
|
|
665
|
+
if client is not None:
|
|
666
|
+
try:
|
|
667
|
+
client.logout()
|
|
668
|
+
except Exception:
|
|
669
|
+
pass
|
|
670
|
+
socket.setdefaulttimeout(previous_timeout)
|
|
671
|
+
except (OSError, socket.timeout, imaplib.IMAP4.error):
|
|
672
|
+
console.print(
|
|
673
|
+
f"[red]✗[/red] IMAP server {host}:{port} unreachable — check your internet connection or imap settings"
|
|
674
|
+
)
|
|
675
|
+
failed += 1
|
|
676
|
+
imap_reachable = False
|
|
677
|
+
except Exception as exc:
|
|
678
|
+
console.print(f"[red]✗[/red] IMAP connectivity: unexpected error — {exc}")
|
|
679
|
+
failed += 1
|
|
680
|
+
|
|
681
|
+
try:
|
|
682
|
+
app_password = load_app_password()
|
|
683
|
+
if app_password:
|
|
684
|
+
console.print("[green]✓[/green] App password stored")
|
|
685
|
+
passed += 1
|
|
686
|
+
else:
|
|
687
|
+
console.print("[red]✗[/red] App password missing — run [bold]otpilot setup[/bold]")
|
|
688
|
+
failed += 1
|
|
689
|
+
except Exception as exc:
|
|
690
|
+
console.print(f"[red]✗[/red] App password stored: unexpected error — {exc}")
|
|
691
|
+
failed += 1
|
|
692
|
+
|
|
693
|
+
try:
|
|
694
|
+
hotkey = get_value("hotkey", "")
|
|
695
|
+
if hotkey == "":
|
|
696
|
+
console.print("[yellow]⚠[/yellow] Hotkey is empty — run [bold]otpilot hotkey[/bold]")
|
|
697
|
+
warnings += 1
|
|
698
|
+
else:
|
|
699
|
+
console.print(f"[green]✓[/green] Hotkey configured: {hotkey}")
|
|
700
|
+
passed += 1
|
|
701
|
+
except Exception as exc:
|
|
702
|
+
console.print(f"[red]✗[/red] Hotkey configured: unexpected error — {exc}")
|
|
703
|
+
failed += 1
|
|
704
|
+
|
|
705
|
+
try:
|
|
706
|
+
if _HOTKEY_LISTENER_AVAILABLE:
|
|
707
|
+
console.print("[green]✓[/green] Hotkey listener available")
|
|
708
|
+
passed += 1
|
|
709
|
+
else:
|
|
710
|
+
console.print(
|
|
711
|
+
"[yellow]⚠[/yellow] Hotkey listener unavailable on this platform — OTPilot will run without hotkey support"
|
|
712
|
+
)
|
|
713
|
+
warnings += 1
|
|
714
|
+
except Exception as exc:
|
|
715
|
+
console.print(f"[red]✗[/red] Hotkey listener available: unexpected error — {exc}")
|
|
716
|
+
failed += 1
|
|
717
|
+
|
|
718
|
+
try:
|
|
719
|
+
if _TRAY_AVAILABLE:
|
|
720
|
+
console.print("[green]✓[/green] System tray available")
|
|
721
|
+
passed += 1
|
|
722
|
+
else:
|
|
723
|
+
console.print(
|
|
724
|
+
"[yellow]⚠[/yellow] System tray unavailable — OTPilot will run headlessly without a tray icon"
|
|
725
|
+
)
|
|
726
|
+
warnings += 1
|
|
727
|
+
except Exception as exc:
|
|
728
|
+
console.print(f"[red]✗[/red] System tray available: unexpected error — {exc}")
|
|
729
|
+
failed += 1
|
|
730
|
+
|
|
731
|
+
try:
|
|
732
|
+
if not os.path.exists(PID_FILE):
|
|
733
|
+
console.print(
|
|
734
|
+
"[yellow]⚠[/yellow] OTPilot is not running — use [bold]otpilot start[/bold]"
|
|
735
|
+
)
|
|
736
|
+
warnings += 1
|
|
737
|
+
else:
|
|
738
|
+
with open(PID_FILE, "r", encoding="utf-8") as pid_file:
|
|
739
|
+
pid_str = pid_file.read().strip()
|
|
740
|
+
pid = int(pid_str)
|
|
741
|
+
try:
|
|
742
|
+
os.kill(pid, 0)
|
|
743
|
+
console.print(f"[green]✓[/green] OTPilot is running (PID {pid})")
|
|
744
|
+
passed += 1
|
|
745
|
+
except ProcessLookupError:
|
|
746
|
+
console.print(
|
|
747
|
+
"[yellow]⚠[/yellow] Stale PID file found "
|
|
748
|
+
f"(process {pid} is gone) — run [bold]otpilot stop[/bold] to clean up"
|
|
749
|
+
)
|
|
750
|
+
warnings += 1
|
|
751
|
+
except Exception as exc:
|
|
752
|
+
console.print(f"[red]✗[/red] OTPilot process running: unexpected error — {exc}")
|
|
753
|
+
failed += 1
|
|
754
|
+
|
|
755
|
+
try:
|
|
756
|
+
latest = _check_for_update()
|
|
757
|
+
if latest:
|
|
758
|
+
console.print(f"[yellow]⚠[/yellow] Update available: v{latest} — run [bold]otpilot update[/bold]")
|
|
759
|
+
warnings += 1
|
|
760
|
+
else:
|
|
761
|
+
if (auth_mode in {"firebase", "credentials"} and gmail_reachable is False) or (
|
|
762
|
+
auth_mode == "imap" and imap_reachable is False
|
|
763
|
+
):
|
|
764
|
+
console.print(
|
|
765
|
+
"[dim] Could not check for updates (no internet or PyPI unreachable)[/dim]"
|
|
766
|
+
)
|
|
767
|
+
else:
|
|
768
|
+
console.print(f"[green]✓[/green] OTPilot v{__version__} is up to date")
|
|
769
|
+
passed += 1
|
|
770
|
+
except Exception as exc:
|
|
771
|
+
console.print(f"[red]✗[/red] Update available: unexpected error — {exc}")
|
|
772
|
+
failed += 1
|
|
773
|
+
|
|
774
|
+
console.print(f"[bold]{passed} passed {warnings} warnings {failed} failed[/bold]")
|
|
775
|
+
if failed > 0:
|
|
776
|
+
console.print(
|
|
777
|
+
"[dim]Run [bold]otpilot setup[/bold] to fix authentication issues, or [bold]otpilot logs[/bold] for runtime errors.[/dim]"
|
|
778
|
+
)
|
|
779
|
+
_print_feedback_prompt()
|
|
780
|
+
|
|
781
|
+
|
|
520
782
|
@cli.command()
|
|
521
783
|
@click.option("--set", "hotkey_value", default=None, help='Set hotkey directly, e.g. --set "ctrl+shift+o"')
|
|
522
784
|
def hotkey(hotkey_value: Optional[str]) -> None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: otpilot
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.4
|
|
4
4
|
Summary: Background CLI utility that copies OTPs from Gmail to clipboard on hotkey trigger
|
|
5
5
|
Author: Jenil
|
|
6
6
|
Author-email: Jenil <mail2jenil.pokar19@gmail.com>
|
|
@@ -261,6 +261,7 @@ Use this when you prefer no OAuth flow inside OTPilot.
|
|
|
261
261
|
| `otpilot history --clear`| Clear OTP history |
|
|
262
262
|
| `otpilot logs` | Tail the OTPilot log file |
|
|
263
263
|
| `otpilot status` | Show auth state, hotkey, and config |
|
|
264
|
+
| `otpilot doctor` | Run diagnostics to validate environment |
|
|
264
265
|
| `otpilot hotkey` | View or reconfigure the global hotkey |
|
|
265
266
|
| `otpilot logout` | Clear stored auth token |
|
|
266
267
|
| `otpilot update` | Check PyPI for updates and upgrade |
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|