diff --git a/README.md b/README.md index 743ec9d..ecefe6c 100644 --- a/README.md +++ b/README.md @@ -206,7 +206,7 @@ python src/main.py ``` When choosing **Add Entry**, you can now select **Password**, **2FA (TOTP)**, - **SSH Key**, or **Seed Phrase**. + **SSH Key**, **Seed Phrase**, or **PGP Key**. ### Adding a 2FA Entry @@ -288,7 +288,8 @@ Back in the Settings menu you can: * Choose `10` to set an additional backup location. * Select `11` to change the inactivity timeout. * Choose `12` to lock the vault and require re-entry of your password. -* Select `13` to view seed profile stats. +* Select `13` to view seed profile stats. The summary lists counts for + passwords, TOTP codes, SSH keys, seed phrases, and PGP keys. * Choose `14` to toggle Secret Mode and set the clipboard clear delay. * Select `15` to return to the main menu. diff --git a/src/main.py b/src/main.py index 43ff691..2e3a493 100644 --- a/src/main.py +++ b/src/main.py @@ -731,7 +731,8 @@ def display_menu( print("2. 2FA (TOTP)") print("3. SSH Key") print("4. Seed Phrase") - print("5. Back") + print("5. PGP Key") + print("6. Back") sub_choice = input("Select entry type: ").strip() password_manager.update_activity() if sub_choice == "1": @@ -747,6 +748,9 @@ def display_menu( password_manager.handle_add_seed() break elif sub_choice == "5": + password_manager.handle_add_pgp() + break + elif sub_choice == "6": break else: print(colored("Invalid choice.", "red")) diff --git a/src/password_manager/manager.py b/src/password_manager/manager.py index 61159bb..a9a8d90 100644 --- a/src/password_manager/manager.py +++ b/src/password_manager/manager.py @@ -1076,6 +1076,40 @@ class PasswordManager: logging.error(f"Error during seed phrase setup: {e}", exc_info=True) print(colored(f"Error: Failed to add seed phrase: {e}", "red")) + def handle_add_pgp(self) -> None: + """Add a PGP key entry and display the generated key.""" + try: + key_type = ( + input("Key type (ed25519 or rsa, default ed25519): ").strip().lower() + or "ed25519" + ) + user_id = input("User ID (optional): ").strip() + notes = input("Notes (optional): ").strip() + index = self.entry_manager.add_pgp_key( + self.parent_seed, + key_type=key_type, + user_id=user_id, + notes=notes, + ) + priv_key, fingerprint = self.entry_manager.get_pgp_key( + index, self.parent_seed + ) + self.is_dirty = True + self.last_update = time.time() + print(colored(f"\n[+] PGP key entry added with ID {index}.\n", "green")) + print(colored(f"Fingerprint: {fingerprint}", "cyan")) + print(priv_key) + try: + self.sync_vault() + except Exception as nostr_error: # pragma: no cover - best effort + logging.error( + f"Failed to post updated index to Nostr: {nostr_error}", + exc_info=True, + ) + except Exception as e: + logging.error(f"Error during PGP key setup: {e}", exc_info=True) + print(colored(f"Error: Failed to add PGP key: {e}", "red")) + def handle_retrieve_entry(self) -> None: """ Handles retrieving a password from the index by prompting the user for the index number @@ -1083,7 +1117,7 @@ class PasswordManager: """ try: index_input = input( - "Enter the index number of the password to retrieve: " + "Enter the index number of the entry to retrieve: " ).strip() if not index_input.isdigit(): print(colored("Error: Index must be a number.", "red")) @@ -2091,7 +2125,7 @@ class PasswordManager: # Entry counts by type data = self.entry_manager.vault.load_index() entries = data.get("entries", {}) - counts: dict[str, int] = {} + counts: dict[str, int] = {etype.value: 0 for etype in EntryType} for entry in entries.values(): etype = entry.get("type", EntryType.PASSWORD.value) counts[etype] = counts.get(etype, 0) + 1 diff --git a/src/tests/test_cli_invalid_input.py b/src/tests/test_cli_invalid_input.py index ec225fc..c3c1f20 100644 --- a/src/tests/test_cli_invalid_input.py +++ b/src/tests/test_cli_invalid_input.py @@ -77,7 +77,7 @@ def test_out_of_range_menu(monkeypatch, capsys): def test_invalid_add_entry_submenu(monkeypatch, capsys): called = {"add": False, "retrieve": False, "modify": False} pm, _ = _make_pm(called) - inputs = iter(["1", "6", "5", "7"]) + inputs = iter(["1", "7", "6", "7"]) monkeypatch.setattr(main, "timed_input", lambda *_: next(inputs)) monkeypatch.setattr("builtins.input", lambda *_: next(inputs)) with pytest.raises(SystemExit):