diff --git a/src/password_manager/manager.py b/src/password_manager/manager.py index 30649c9..cb502f1 100644 --- a/src/password_manager/manager.py +++ b/src/password_manager/manager.py @@ -1542,6 +1542,93 @@ class PasswordManager: self.is_dirty = True self.last_update = time.time() + def _entry_actions_menu(self, index: int, entry: dict) -> None: + """Provide actions for a retrieved entry.""" + while True: + archived = entry.get("archived", entry.get("blacklisted", False)) + print(colored("\n[+] Entry Actions:", "green")) + if archived: + print(colored("U. Unarchive", "cyan")) + else: + print(colored("A. Archive", "cyan")) + print(colored("N. Add Note", "cyan")) + print(colored("C. Add Custom Field", "cyan")) + print(colored("H. Add Hidden Field", "cyan")) + print(colored("E. Edit", "cyan")) + + choice = ( + input("Select an action or press Enter to return: ").strip().lower() + ) + if not choice: + break + if choice == "a" and not archived: + self.entry_manager.archive_entry(index) + self.is_dirty = True + self.last_update = time.time() + elif choice == "u" and archived: + self.entry_manager.restore_entry(index) + self.is_dirty = True + self.last_update = time.time() + elif choice == "n": + note = input("Enter note: ").strip() + if note: + notes = entry.get("notes", "") + notes = f"{notes}\n{note}" if notes else note + self.entry_manager.modify_entry(index, notes=notes) + self.is_dirty = True + self.last_update = time.time() + elif choice in {"c", "h"}: + label = input(" Field label: ").strip() + if not label: + print(colored("Field label cannot be empty.", "red")) + else: + value = input(" Field value: ").strip() + hidden = choice == "h" + custom_fields = entry.get("custom_fields", []) + custom_fields.append( + {"label": label, "value": value, "is_hidden": hidden} + ) + self.entry_manager.modify_entry(index, custom_fields=custom_fields) + self.is_dirty = True + self.last_update = time.time() + elif choice == "e": + self._entry_edit_menu(index, entry) + else: + print(colored("Invalid choice.", "red")) + entry = self.entry_manager.retrieve_entry(index) or entry + + def _entry_edit_menu(self, index: int, entry: dict) -> None: + """Sub-menu for editing common entry fields.""" + entry_type = entry.get("type", EntryType.PASSWORD.value) + while True: + print(colored("\n[+] Edit Menu:", "green")) + print(colored("L. Edit Label", "cyan")) + if entry_type == EntryType.PASSWORD.value: + print(colored("U. Edit Username", "cyan")) + print(colored("R. Edit URL", "cyan")) + choice = input("Select option or press Enter to go back: ").strip().lower() + if not choice: + break + if choice == "l": + new_label = input("New label: ").strip() + if new_label: + self.entry_manager.modify_entry(index, label=new_label) + self.is_dirty = True + self.last_update = time.time() + elif entry_type == EntryType.PASSWORD.value and choice == "u": + new_username = input("New username: ").strip() + self.entry_manager.modify_entry(index, username=new_username) + self.is_dirty = True + self.last_update = time.time() + elif entry_type == EntryType.PASSWORD.value and choice == "r": + new_url = input("New URL: ").strip() + self.entry_manager.modify_entry(index, url=new_url) + self.is_dirty = True + self.last_update = time.time() + else: + print(colored("Invalid choice.", "red")) + entry = self.entry_manager.retrieve_entry(index) or entry + def handle_retrieve_entry(self) -> None: """ Handles retrieving a password from the index by prompting the user for the index number @@ -1625,7 +1712,7 @@ class PasswordManager: 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._prompt_toggle_archive(entry, index) + self._entry_actions_menu(index, entry) pause() return if entry_type == EntryType.SSH.value: @@ -1661,7 +1748,7 @@ class PasswordManager: 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")) - self._prompt_toggle_archive(entry, index) + self._entry_actions_menu(index, entry) pause() return if entry_type == EntryType.SEED.value: @@ -1712,7 +1799,7 @@ class PasswordManager: 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")) - self._prompt_toggle_archive(entry, index) + self._entry_actions_menu(index, entry) pause() return if entry_type == EntryType.PGP.value: @@ -1746,7 +1833,7 @@ class PasswordManager: 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")) - self._prompt_toggle_archive(entry, index) + self._entry_actions_menu(index, entry) pause() return if entry_type == EntryType.NOSTR.value: @@ -1780,7 +1867,7 @@ class PasswordManager: 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")) - self._prompt_toggle_archive(entry, index) + self._entry_actions_menu(index, entry) pause() return @@ -1837,7 +1924,7 @@ class PasswordManager: ) else: print(colored(f" {f_label}: {f_value}", "cyan")) - self._prompt_toggle_archive(entry, index) + self._entry_actions_menu(index, entry) pause() return if entry_type == EntryType.MANAGED_ACCOUNT.value: @@ -1881,13 +1968,13 @@ class PasswordManager: from password_manager.seedqr import encode_seedqr TotpManager.print_qr_code(encode_seedqr(seed)) - self._prompt_toggle_archive(entry, index) + self._entry_actions_menu(index, entry) pause() return if action == "l": self.load_managed_account(index) return - self._prompt_toggle_archive(entry, index) + self._entry_actions_menu(index, entry) pause() return @@ -1975,7 +2062,7 @@ class PasswordManager: print(colored(f" {label}: {value}", "cyan")) else: print(colored("Error: Failed to retrieve the password.", "red")) - self._prompt_toggle_archive(entry, index) + self._entry_actions_menu(index, entry) pause() except Exception as e: logging.error(f"Error during password retrieval: {e}", exc_info=True) @@ -2625,7 +2712,7 @@ class PasswordManager: .lower() ) if action == "v": - self.display_entry_details(entry_index) + self.show_entry_details_by_index(entry_index) pause() elif action == "r": self.entry_manager.restore_entry(entry_index) diff --git a/src/tests/test_archive_from_retrieve.py b/src/tests/test_archive_from_retrieve.py index 2383503..779d3f3 100644 --- a/src/tests/test_archive_from_retrieve.py +++ b/src/tests/test_archive_from_retrieve.py @@ -40,7 +40,7 @@ def test_archive_entry_from_retrieve(monkeypatch): index = entry_mgr.add_entry("example.com", 8) - inputs = iter([str(index), "y"]) + inputs = iter([str(index), "a", ""]) monkeypatch.setattr("builtins.input", lambda *a, **k: next(inputs)) pm.handle_retrieve_entry() @@ -72,7 +72,7 @@ def test_restore_entry_from_retrieve(monkeypatch): index = entry_mgr.add_entry("example.com", 8) entry_mgr.archive_entry(index) - inputs = iter([str(index), "y"]) + inputs = iter([str(index), "u", ""]) monkeypatch.setattr("builtins.input", lambda *a, **k: next(inputs)) pm.handle_retrieve_entry() diff --git a/src/tests/test_manager_workflow.py b/src/tests/test_manager_workflow.py index 5a95cd1..c12f39e 100644 --- a/src/tests/test_manager_workflow.py +++ b/src/tests/test_manager_workflow.py @@ -57,7 +57,7 @@ def test_manager_workflow(monkeypatch): "n", # add custom field "", # length (default) "0", # retrieve index - "n", # archive entry prompt + "", # no action in entry menu "0", # modify index "", # new label "user", # new username