diff --git a/src/password_manager/manager.py b/src/password_manager/manager.py index d60cee9..d7a1ff0 100644 --- a/src/password_manager/manager.py +++ b/src/password_manager/manager.py @@ -153,6 +153,7 @@ class PasswordManager: self.profile_stack: list[tuple[str, Path, str]] = [] self.last_unlock_duration: float | None = None self.verbose_timing: bool = False + self._suppress_entry_actions_menu: bool = False # Initialize the fingerprint manager first self.initialize_fingerprint_manager() @@ -2053,11 +2054,422 @@ class PasswordManager: logging.error(f"Error displaying QR menu: {e}", exc_info=True) print(colored(f"Error: Failed to display QR codes: {e}", "red")) + def display_sensitive_entry_info(self, entry: dict, index: int) -> None: + """Display information for a sensitive entry. + + Parameters + ---------- + entry: dict + Entry data retrieved from the vault. + index: int + Index of the entry being displayed. + """ + + self._suppress_entry_actions_menu = False + + entry_type = entry.get("type", entry.get("kind", EntryType.PASSWORD.value)) + if isinstance(entry_type, str): + entry_type = entry_type.lower() + + if entry_type == EntryType.TOTP.value: + label = entry.get("label", "") + period = int(entry.get("period", 30)) + notes = entry.get("notes", "") + print(colored(f"Retrieving 2FA code for '{label}'.", "cyan")) + print(colored("Press Enter to return to the menu.", "cyan")) + try: + while True: + code = self.entry_manager.get_totp_code(index, self.parent_seed) + if self.secret_mode_enabled: + copy_to_clipboard(code, self.clipboard_clear_delay) + print( + colored( + f"[+] 2FA code for '{label}' copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", + "green", + ) + ) + else: + print(colored("\n[+] Retrieved 2FA Code:\n", "green")) + print(colored(f"Label: {label}", "cyan")) + imported = "secret" in entry + category = "imported" if imported else "deterministic" + print(color_text(f"Code: {code}", category)) + if notes: + print(colored(f"Notes: {notes}", "cyan")) + tags = entry.get("tags", []) + if tags: + print(colored(f"Tags: {', '.join(tags)}", "cyan")) + remaining = self.entry_manager.get_totp_time_remaining(index) + exit_loop = False + while remaining > 0: + filled = int(20 * (period - remaining) / period) + bar = "[" + "#" * filled + "-" * (20 - filled) + "]" + sys.stdout.write(f"\r{bar} {remaining:2d}s") + sys.stdout.flush() + try: + user_input = timed_input("", 1) + if ( + user_input.strip() == "" + or user_input.strip().lower() == "b" + ): + exit_loop = True + break + except TimeoutError: + pass + except KeyboardInterrupt: + exit_loop = True + print() + break + remaining -= 1 + sys.stdout.write("\n") + sys.stdout.flush() + if exit_loop: + break + except Exception as e: # pragma: no cover - best effort + logging.error(f"Error generating TOTP code: {e}", exc_info=True) + print(colored(f"Error: Failed to generate TOTP code: {e}", "red")) + return + + if entry_type == EntryType.SSH.value: + notes = entry.get("notes", "") + label = entry.get("label", "") + if not confirm_action( + "WARNING: Displaying SSH keys reveals sensitive information. Continue? (Y/N): " + ): + self.notify("SSH key display cancelled.", level="WARNING") + return + try: + priv_pem, pub_pem = self.entry_manager.get_ssh_key_pair( + index, self.parent_seed + ) + print(colored("\n[+] Retrieved SSH Key Pair:\n", "green")) + if label: + print(colored(f"Label: {label}", "cyan")) + if notes: + print(colored(f"Notes: {notes}", "cyan")) + tags = entry.get("tags", []) + if tags: + print(colored(f"Tags: {', '.join(tags)}", "cyan")) + print(colored("Public Key:", "cyan")) + print(color_text(pub_pem, "default")) + if self.secret_mode_enabled: + copy_to_clipboard(priv_pem, self.clipboard_clear_delay) + print( + colored( + f"[+] SSH private key copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", + "green", + ) + ) + else: + print(colored("Private Key:", "cyan")) + print(color_text(priv_pem, "deterministic")) + except Exception as e: # pragma: no cover - best effort + logging.error(f"Error deriving SSH key pair: {e}", exc_info=True) + print(colored(f"Error: Failed to derive SSH keys: {e}", "red")) + return + + if entry_type == EntryType.SEED.value: + notes = entry.get("notes", "") + label = entry.get("label", "") + if not confirm_action( + "WARNING: Displaying the seed phrase reveals sensitive information. Continue? (Y/N): " + ): + self.notify("Seed phrase display cancelled.", level="WARNING") + return + try: + phrase = self.entry_manager.get_seed_phrase(index, self.parent_seed) + print(colored("\n[+] Retrieved Seed Phrase:\n", "green")) + print(colored(f"Index: {index}", "cyan")) + if label: + print(colored(f"Label: {label}", "cyan")) + if notes: + print(colored(f"Notes: {notes}", "cyan")) + tags = entry.get("tags", []) + if tags: + print(colored(f"Tags: {', '.join(tags)}", "cyan")) + if self.secret_mode_enabled: + copy_to_clipboard(phrase, self.clipboard_clear_delay) + print( + colored( + f"[+] Seed phrase copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", + "green", + ) + ) + else: + print(color_text(phrase, "deterministic")) + if confirm_action("Show derived entropy as hex? (Y/N): "): + from local_bip85.bip85 import BIP85 + from bip_utils import Bip39SeedGenerator + + words = int(entry.get("word_count", entry.get("words", 24))) + bytes_len = {12: 16, 18: 24, 24: 32}.get(words, 32) + seed_bytes = Bip39SeedGenerator(self.parent_seed).Generate() + bip85 = BIP85(seed_bytes) + entropy = bip85.derive_entropy( + index=int(entry.get("index", index)), + bytes_len=bytes_len, + app_no=39, + words_len=words, + ) + print(color_text(f"Entropy: {entropy.hex()}", "deterministic")) + except Exception as e: # pragma: no cover - best effort + logging.error(f"Error deriving seed phrase: {e}", exc_info=True) + print(colored(f"Error: Failed to derive seed phrase: {e}", "red")) + return + + if entry_type == EntryType.PGP.value: + notes = entry.get("notes", "") + label = entry.get("user_id", "") + if not confirm_action( + "WARNING: Displaying the PGP key reveals sensitive information. Continue? (Y/N): " + ): + self.notify("PGP key display cancelled.", level="WARNING") + return + try: + priv_key, fingerprint = self.entry_manager.get_pgp_key( + index, self.parent_seed + ) + print(colored("\n[+] Retrieved PGP Key:\n", "green")) + if label: + print(colored(f"User ID: {label}", "cyan")) + if notes: + print(colored(f"Notes: {notes}", "cyan")) + tags = entry.get("tags", []) + if tags: + print(colored(f"Tags: {', '.join(tags)}", "cyan")) + print(colored(f"Fingerprint: {fingerprint}", "cyan")) + if self.secret_mode_enabled: + copy_to_clipboard(priv_key, self.clipboard_clear_delay) + print( + colored( + f"[+] PGP key copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", + "green", + ) + ) + else: + print(color_text(priv_key, "deterministic")) + except Exception as e: # pragma: no cover - best effort + logging.error(f"Error deriving PGP key: {e}", exc_info=True) + print(colored(f"Error: Failed to derive PGP key: {e}", "red")) + return + + if entry_type == EntryType.NOSTR.value: + label = entry.get("label", "") + notes = entry.get("notes", "") + try: + npub, nsec = self.entry_manager.get_nostr_key_pair( + index, self.parent_seed + ) + print(colored("\n[+] Retrieved Nostr Keys:\n", "green")) + print(colored(f"Label: {label}", "cyan")) + print(colored(f"npub: {npub}", "cyan")) + if self.secret_mode_enabled: + copy_to_clipboard(nsec, self.clipboard_clear_delay) + print( + colored( + f"[+] nsec copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", + "green", + ) + ) + else: + print(color_text(f"nsec: {nsec}", "deterministic")) + if notes: + print(colored(f"Notes: {notes}", "cyan")) + tags = entry.get("tags", []) + if tags: + print(colored(f"Tags: {', '.join(tags)}", "cyan")) + except Exception as e: # pragma: no cover - best effort + logging.error(f"Error deriving Nostr keys: {e}", exc_info=True) + print(colored(f"Error: Failed to derive Nostr keys: {e}", "red")) + return + + if entry_type == EntryType.KEY_VALUE.value: + label = entry.get("label", "") + value = entry.get("value", "") + notes = entry.get("notes", "") + archived = entry.get("archived", False) + print(colored(f"Retrieving value for '{label}'.", "cyan")) + if notes: + print(colored(f"Notes: {notes}", "cyan")) + tags = entry.get("tags", []) + if tags: + print(colored(f"Tags: {', '.join(tags)}", "cyan")) + print( + colored( + f"Archived Status: {'Archived' if archived else 'Active'}", "cyan" + ) + ) + if self.secret_mode_enabled: + copy_to_clipboard(value, self.clipboard_clear_delay) + print( + colored( + f"[+] Value copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", + "green", + ) + ) + else: + print(color_text(f"Value: {value}", "deterministic")) + + custom_fields = entry.get("custom_fields", []) + if custom_fields: + print(colored("Additional Fields:", "cyan")) + hidden_fields = [] + for field in custom_fields: + f_label = field.get("label", "") + f_value = field.get("value", "") + if field.get("is_hidden"): + hidden_fields.append((f_label, f_value)) + print(colored(f" {f_label}: [hidden]", "cyan")) + else: + print(colored(f" {f_label}: {f_value}", "cyan")) + if hidden_fields: + show = input("Reveal hidden fields? (y/N): ").strip().lower() + if show == "y": + for f_label, f_value in hidden_fields: + if self.secret_mode_enabled: + copy_to_clipboard(f_value, self.clipboard_clear_delay) + print( + colored( + f"[+] {f_label} copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", + "green", + ) + ) + else: + print(colored(f" {f_label}: {f_value}", "cyan")) + return + + if entry_type == EntryType.MANAGED_ACCOUNT.value: + label = entry.get("label", "") + notes = entry.get("notes", "") + archived = entry.get("archived", False) + fingerprint = entry.get("fingerprint", "") + print(colored(f"Managed account '{label}'.", "cyan")) + if notes: + print(colored(f"Notes: {notes}", "cyan")) + if fingerprint: + print(colored(f"Fingerprint: {fingerprint}", "cyan")) + tags = entry.get("tags", []) + if tags: + print(colored(f"Tags: {', '.join(tags)}", "cyan")) + print( + colored( + f"Archived Status: {'Archived' if archived else 'Active'}", "cyan" + ) + ) + action = ( + input( + "Enter 'r' to reveal seed, 'l' to load account, or press Enter to go back: " + ) + .strip() + .lower() + ) + if action == "r": + seed = self.entry_manager.get_managed_account_seed( + index, self.parent_seed + ) + if self.secret_mode_enabled: + copy_to_clipboard(seed, self.clipboard_clear_delay) + print( + colored( + f"[+] Seed phrase copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", + "green", + ) + ) + else: + print(color_text(seed, "deterministic")) + return + if action == "l": + self._suppress_entry_actions_menu = True + self.load_managed_account(index) + return + return + + # Default: PASSWORD + website_name = entry.get("label", entry.get("website")) + length = entry.get("length") + username = entry.get("username") + url = entry.get("url") + blacklisted = entry.get("archived", entry.get("blacklisted")) + notes = entry.get("notes", "") + + print( + colored( + f"Retrieving password for '{website_name}' with length {length}.", + "cyan", + ) + ) + if username: + print(colored(f"Username: {username}", "cyan")) + if url: + print(colored(f"URL: {url}", "cyan")) + if blacklisted: + self.notify( + "Warning: This password is archived and should not be used.", + level="WARNING", + ) + + password = self.password_generator.generate_password(length, index) + + if password: + if self.secret_mode_enabled: + copy_to_clipboard(password, self.clipboard_clear_delay) + print( + colored( + f"[+] Password for '{website_name}' copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", + "green", + ) + ) + else: + print( + colored( + f"\n[+] Retrieved Password for {website_name}:\n", + "green", + ) + ) + print(color_text(f"Password: {password}", "deterministic")) + print(colored(f"Associated Username: {username or 'N/A'}", "cyan")) + print(colored(f"Associated URL: {url or 'N/A'}", "cyan")) + print( + colored( + f"Archived Status: {'Archived' if blacklisted else 'Active'}", + "cyan", + ) + ) + tags = entry.get("tags", []) + if tags: + print(colored(f"Tags: {', '.join(tags)}", "cyan")) + custom_fields = entry.get("custom_fields", []) + if custom_fields: + print(colored("Additional Fields:", "cyan")) + hidden_fields = [] + for field in custom_fields: + label = field.get("label", "") + value = field.get("value", "") + if field.get("is_hidden"): + hidden_fields.append((label, value)) + print(colored(f" {label}: [hidden]", "cyan")) + else: + print(colored(f" {label}: {value}", "cyan")) + if hidden_fields: + show = input("Reveal hidden fields? (y/N): ").strip().lower() + if show == "y": + for label, value in hidden_fields: + if self.secret_mode_enabled: + copy_to_clipboard(value, self.clipboard_clear_delay) + print( + colored( + f"[+] {label} copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", + "green", + ) + ) + else: + print(colored(f" {label}: {value}", "cyan")) + else: + print(colored("Error: Failed to retrieve the password.", "red")) + return + def handle_retrieve_entry(self) -> None: - """ - Handles retrieving a password from the index by prompting the user for the index number - and displaying the corresponding password and associated details. - """ + """Prompt for an index and display the corresponding entry.""" try: fp, parent_fp, child_fp = self.header_fingerprint_args clear_header_with_notification( @@ -2081,431 +2493,15 @@ class PasswordManager: pause() return - entry_type = entry.get("type", entry.get("kind", EntryType.PASSWORD.value)) - if isinstance(entry_type, str): - entry_type = entry_type.lower() + self.display_sensitive_entry_info(entry, index) + pause() - if entry_type == EntryType.TOTP.value: - label = entry.get("label", "") - period = int(entry.get("period", 30)) - notes = entry.get("notes", "") - print(colored(f"Retrieving 2FA code for '{label}'.", "cyan")) - print(colored("Press Enter to return to the menu.", "cyan")) - try: - while True: - code = self.entry_manager.get_totp_code(index, self.parent_seed) - if self.secret_mode_enabled: - copy_to_clipboard(code, self.clipboard_clear_delay) - print( - colored( - f"[+] 2FA code for '{label}' copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", - "green", - ) - ) - else: - print(colored("\n[+] Retrieved 2FA Code:\n", "green")) - print(colored(f"Label: {label}", "cyan")) - imported = "secret" in entry - category = "imported" if imported else "deterministic" - print(color_text(f"Code: {code}", category)) - if notes: - print(colored(f"Notes: {notes}", "cyan")) - tags = entry.get("tags", []) - if tags: - print(colored(f"Tags: {', '.join(tags)}", "cyan")) - remaining = self.entry_manager.get_totp_time_remaining(index) - exit_loop = False - while remaining > 0: - filled = int(20 * (period - remaining) / period) - bar = "[" + "#" * filled + "-" * (20 - filled) + "]" - sys.stdout.write(f"\r{bar} {remaining:2d}s") - sys.stdout.flush() - try: - user_input = timed_input("", 1) - if ( - user_input.strip() == "" - or user_input.strip().lower() == "b" - ): - exit_loop = True - break - except TimeoutError: - pass - except KeyboardInterrupt: - exit_loop = True - print() - break - remaining -= 1 - sys.stdout.write("\n") - sys.stdout.flush() - if exit_loop: - break - except Exception as e: - logging.error(f"Error generating TOTP code: {e}", exc_info=True) - print(colored(f"Error: Failed to generate TOTP code: {e}", "red")) - self._entry_actions_menu(index, entry) - pause() - return - if entry_type == EntryType.SSH.value: - notes = entry.get("notes", "") - label = entry.get("label", "") - if not confirm_action( - "WARNING: Displaying SSH keys reveals sensitive information. Continue? (Y/N): " - ): - self.notify("SSH key display cancelled.", level="WARNING") - return - try: - priv_pem, pub_pem = self.entry_manager.get_ssh_key_pair( - index, self.parent_seed - ) - print(colored("\n[+] Retrieved SSH Key Pair:\n", "green")) - if label: - print(colored(f"Label: {label}", "cyan")) - if notes: - print(colored(f"Notes: {notes}", "cyan")) - tags = entry.get("tags", []) - if tags: - print(colored(f"Tags: {', '.join(tags)}", "cyan")) - print(colored("Public Key:", "cyan")) - print(color_text(pub_pem, "default")) - if self.secret_mode_enabled: - copy_to_clipboard(priv_pem, self.clipboard_clear_delay) - print( - colored( - f"[+] SSH private key copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", - "green", - ) - ) - else: - print(colored("Private Key:", "cyan")) - print(color_text(priv_pem, "deterministic")) - except Exception as e: - logging.error(f"Error deriving SSH key pair: {e}", exc_info=True) - print(colored(f"Error: Failed to derive SSH keys: {e}", "red")) - pause() - self._entry_actions_menu(index, entry) - pause() - return - if entry_type == EntryType.SEED.value: - notes = entry.get("notes", "") - label = entry.get("label", "") - if not confirm_action( - "WARNING: Displaying the seed phrase reveals sensitive information. Continue? (Y/N): " - ): - self.notify("Seed phrase display cancelled.", level="WARNING") - return - try: - phrase = self.entry_manager.get_seed_phrase(index, self.parent_seed) - print(colored("\n[+] Retrieved Seed Phrase:\n", "green")) - print(colored(f"Index: {index}", "cyan")) - if label: - print(colored(f"Label: {label}", "cyan")) - if notes: - print(colored(f"Notes: {notes}", "cyan")) - tags = entry.get("tags", []) - if tags: - print(colored(f"Tags: {', '.join(tags)}", "cyan")) - if self.secret_mode_enabled: - copy_to_clipboard(phrase, self.clipboard_clear_delay) - print( - colored( - f"[+] Seed phrase copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", - "green", - ) - ) - else: - print(color_text(phrase, "deterministic")) - # Removed QR code display prompt and output - if confirm_action("Show derived entropy as hex? (Y/N): "): - from local_bip85.bip85 import BIP85 - from bip_utils import Bip39SeedGenerator - - words = int(entry.get("word_count", entry.get("words", 24))) - bytes_len = {12: 16, 18: 24, 24: 32}.get(words, 32) - seed_bytes = Bip39SeedGenerator(self.parent_seed).Generate() - bip85 = BIP85(seed_bytes) - entropy = bip85.derive_entropy( - index=int(entry.get("index", index)), - bytes_len=bytes_len, - app_no=39, - words_len=words, - ) - print(color_text(f"Entropy: {entropy.hex()}", "deterministic")) - except Exception as e: - logging.error(f"Error deriving seed phrase: {e}", exc_info=True) - print(colored(f"Error: Failed to derive seed phrase: {e}", "red")) - pause() - self._entry_actions_menu(index, entry) - pause() - return - if entry_type == EntryType.PGP.value: - notes = entry.get("notes", "") - label = entry.get("user_id", "") - if not confirm_action( - "WARNING: Displaying the PGP key reveals sensitive information. Continue? (Y/N): " - ): - self.notify("PGP key display cancelled.", level="WARNING") - return - try: - priv_key, fingerprint = self.entry_manager.get_pgp_key( - index, self.parent_seed - ) - print(colored("\n[+] Retrieved PGP Key:\n", "green")) - if label: - print(colored(f"User ID: {label}", "cyan")) - if notes: - print(colored(f"Notes: {notes}", "cyan")) - tags = entry.get("tags", []) - if tags: - print(colored(f"Tags: {', '.join(tags)}", "cyan")) - print(colored(f"Fingerprint: {fingerprint}", "cyan")) - if self.secret_mode_enabled: - copy_to_clipboard(priv_key, self.clipboard_clear_delay) - print( - colored( - f"[+] PGP key copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", - "green", - ) - ) - else: - print(color_text(priv_key, "deterministic")) - except Exception as e: - logging.error(f"Error deriving PGP key: {e}", exc_info=True) - print(colored(f"Error: Failed to derive PGP key: {e}", "red")) - pause() - self._entry_actions_menu(index, entry) - pause() - return - if entry_type == EntryType.NOSTR.value: - label = entry.get("label", "") - notes = entry.get("notes", "") - try: - npub, nsec = self.entry_manager.get_nostr_key_pair( - index, self.parent_seed - ) - print(colored("\n[+] Retrieved Nostr Keys:\n", "green")) - print(colored(f"Label: {label}", "cyan")) - print(colored(f"npub: {npub}", "cyan")) - if self.secret_mode_enabled: - copy_to_clipboard(nsec, self.clipboard_clear_delay) - print( - colored( - f"[+] nsec copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", - "green", - ) - ) - else: - print(color_text(f"nsec: {nsec}", "deterministic")) - # QR code display removed for npub and nsec - if notes: - print(colored(f"Notes: {notes}", "cyan")) - tags = entry.get("tags", []) - if tags: - print(colored(f"Tags: {', '.join(tags)}", "cyan")) - except Exception as e: - logging.error(f"Error deriving Nostr keys: {e}", exc_info=True) - print(colored(f"Error: Failed to derive Nostr keys: {e}", "red")) - pause() - self._entry_actions_menu(index, entry) - pause() + if getattr(self, "_suppress_entry_actions_menu", False): return - if entry_type == EntryType.KEY_VALUE.value: - label = entry.get("label", "") - value = entry.get("value", "") - notes = entry.get("notes", "") - archived = entry.get("archived", False) - print(colored(f"Retrieving value for '{label}'.", "cyan")) - if notes: - print(colored(f"Notes: {notes}", "cyan")) - tags = entry.get("tags", []) - if tags: - print(colored(f"Tags: {', '.join(tags)}", "cyan")) - print( - colored( - f"Archived Status: {'Archived' if archived else 'Active'}", - "cyan", - ) - ) - if self.secret_mode_enabled: - copy_to_clipboard(value, self.clipboard_clear_delay) - print( - colored( - f"[+] Value copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", - "green", - ) - ) - else: - print(color_text(f"Value: {value}", "deterministic")) - - custom_fields = entry.get("custom_fields", []) - if custom_fields: - print(colored("Additional Fields:", "cyan")) - hidden_fields = [] - for field in custom_fields: - f_label = field.get("label", "") - f_value = field.get("value", "") - if field.get("is_hidden"): - hidden_fields.append((f_label, f_value)) - print(colored(f" {f_label}: [hidden]", "cyan")) - else: - print(colored(f" {f_label}: {f_value}", "cyan")) - if hidden_fields: - show = input("Reveal hidden fields? (y/N): ").strip().lower() - if show == "y": - for f_label, f_value in hidden_fields: - if self.secret_mode_enabled: - copy_to_clipboard( - f_value, self.clipboard_clear_delay - ) - print( - colored( - f"[+] {f_label} copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", - "green", - ) - ) - else: - print(colored(f" {f_label}: {f_value}", "cyan")) - self._entry_actions_menu(index, entry) - pause() - return - if entry_type == EntryType.MANAGED_ACCOUNT.value: - label = entry.get("label", "") - notes = entry.get("notes", "") - archived = entry.get("archived", False) - fingerprint = entry.get("fingerprint", "") - print(colored(f"Managed account '{label}'.", "cyan")) - if notes: - print(colored(f"Notes: {notes}", "cyan")) - if fingerprint: - print(colored(f"Fingerprint: {fingerprint}", "cyan")) - tags = entry.get("tags", []) - if tags: - print(colored(f"Tags: {', '.join(tags)}", "cyan")) - print( - colored( - f"Archived Status: {'Archived' if archived else 'Active'}", - "cyan", - ) - ) - action = ( - input( - "Enter 'r' to reveal seed, 'l' to load account, or press Enter to go back: " - ) - .strip() - .lower() - ) - if action == "r": - seed = self.entry_manager.get_managed_account_seed( - index, self.parent_seed - ) - if self.secret_mode_enabled: - copy_to_clipboard(seed, self.clipboard_clear_delay) - print( - colored( - f"[+] Seed phrase copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", - "green", - ) - ) - else: - print(color_text(seed, "deterministic")) - # QR code display removed for managed account seed - self._entry_actions_menu(index, entry) - pause() - return - if action == "l": - self.load_managed_account(index) - return - self._entry_actions_menu(index, entry) - pause() - return - - website_name = entry.get("label", entry.get("website")) - length = entry.get("length") - username = entry.get("username") - url = entry.get("url") - blacklisted = entry.get("archived", entry.get("blacklisted")) - notes = entry.get("notes", "") - - print( - colored( - f"Retrieving password for '{website_name}' with length {length}.", - "cyan", - ) - ) - if username: - print(colored(f"Username: {username}", "cyan")) - if url: - print(colored(f"URL: {url}", "cyan")) - if blacklisted: - self.notify( - "Warning: This password is archived and should not be used.", - level="WARNING", - ) - - password = self.password_generator.generate_password(length, index) - - if password: - if self.secret_mode_enabled: - copy_to_clipboard(password, self.clipboard_clear_delay) - print( - colored( - f"[+] Password for '{website_name}' copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", - "green", - ) - ) - else: - print( - colored( - f"\n[+] Retrieved Password for {website_name}:\n", - "green", - ) - ) - print(color_text(f"Password: {password}", "deterministic")) - print(colored(f"Associated Username: {username or 'N/A'}", "cyan")) - print(colored(f"Associated URL: {url or 'N/A'}", "cyan")) - print( - colored( - f"Archived Status: {'Archived' if blacklisted else 'Active'}", - "cyan", - ) - ) - tags = entry.get("tags", []) - if tags: - print(colored(f"Tags: {', '.join(tags)}", "cyan")) - custom_fields = entry.get("custom_fields", []) - if custom_fields: - print(colored("Additional Fields:", "cyan")) - hidden_fields = [] - for field in custom_fields: - label = field.get("label", "") - value = field.get("value", "") - if field.get("is_hidden"): - hidden_fields.append((label, value)) - print(colored(f" {label}: [hidden]", "cyan")) - else: - print(colored(f" {label}: {value}", "cyan")) - if hidden_fields: - show = ( - input("Reveal hidden fields? (y/N): ").strip().lower() - ) - if show == "y": - for label, value in hidden_fields: - if self.secret_mode_enabled: - copy_to_clipboard( - value, self.clipboard_clear_delay - ) - print( - colored( - f"[+] {label} copied to clipboard. Will clear in {self.clipboard_clear_delay} seconds.", - "green", - ) - ) - else: - print(colored(f" {label}: {value}", "cyan")) - else: - print(colored("Error: Failed to retrieve the password.", "red")) self._entry_actions_menu(index, entry) pause() + return except Exception as e: logging.error(f"Error during password retrieval: {e}", exc_info=True) print(colored(f"Error: Failed to retrieve password: {e}", "red")) @@ -3914,9 +3910,9 @@ class PasswordManager: ) stats["backup_count"] = len(backups) stats["backup_dir"] = str(self.backup_manager.backup_dir) - stats["additional_backup_path"] = ( - self.config_manager.get_additional_backup_path() - ) + stats[ + "additional_backup_path" + ] = self.config_manager.get_additional_backup_path() # Nostr sync info manifest = getattr(self.nostr_client, "current_manifest", None)