Add custom seed profile names

This commit is contained in:
thePR0M3TH3AN
2025-07-15 21:56:31 -04:00
parent 40bd009b6e
commit 113fd1181a
6 changed files with 111 additions and 22 deletions

View File

@@ -378,8 +378,12 @@ SeedPass allows you to manage multiple seed profiles (previously referred to as
3. Enter the number corresponding to the seed profile you wish to switch to.
4. Enter the master password associated with that seed profile.
- **List All Seed Profiles:**
- **List All Seed Profiles:**
In the **Profiles** menu, choose "List All Seed Profiles" to view all existing profiles.
- **Set Seed Profile Name:**
In the **Profiles** menu, choose "Set Seed Profile Name" to assign an optional
label to the currently selected profile. The name is stored locally and shown
alongside the fingerprint in menus.
**Note:** The term "seed profile" is used to represent different sets of seeds you can manage within SeedPass. This provides an intuitive way to handle multiple identities or sets of passwords.

View File

@@ -368,6 +368,8 @@ SeedPass allows you to manage multiple seed profiles (previously referred to as
- **List All Seed Profiles:**
- In the **Profiles** menu, choose "List All Seed Profiles" to view all existing profiles.
- **Set Seed Profile Name:**
- In the **Profiles** menu, choose "Set Seed Profile Name" to assign a label to the current profile. The name is stored locally and shown next to the fingerprint.
**Note:** The term "seed profile" is used to represent different sets of seeds you can manage within SeedPass. This provides an intuitive way to handle multiple identities or sets of passwords.

View File

@@ -151,7 +151,8 @@ def handle_switch_fingerprint(password_manager: PasswordManager):
print(colored("Available Seed Profiles:", "cyan"))
for idx, fp in enumerate(fingerprints, start=1):
print(colored(f"{idx}. {fp}", "cyan"))
label = password_manager.fingerprint_manager.display_name(fp)
print(colored(f"{idx}. {label}", "cyan"))
choice = input("Select a seed profile by number to switch: ").strip()
if not choice.isdigit() or not (1 <= int(choice) <= len(fingerprints)):
@@ -195,7 +196,8 @@ def handle_remove_fingerprint(password_manager: PasswordManager):
print(colored("Available Seed Profiles:", "cyan"))
for idx, fp in enumerate(fingerprints, start=1):
print(colored(f"{idx}. {fp}", "cyan"))
label = password_manager.fingerprint_manager.display_name(fp)
print(colored(f"{idx}. {label}", "cyan"))
choice = input("Select a seed profile by number to remove: ").strip()
if not choice.isdigit() or not (1 <= int(choice) <= len(fingerprints)):
@@ -239,7 +241,8 @@ def handle_list_fingerprints(password_manager: PasswordManager):
print(colored("Available Seed Profiles:", "cyan"))
for fp in fingerprints:
print(colored(f"- {fp}", "cyan"))
label = password_manager.fingerprint_manager.display_name(fp)
print(colored(f"- {label}", "cyan"))
pause()
except Exception as e:
logging.error(f"Error listing seed profiles: {e}", exc_info=True)
@@ -641,6 +644,25 @@ def handle_set_additional_backup_location(pm: PasswordManager) -> None:
print(colored(f"Error: {e}", "red"))
def handle_set_profile_name(pm: PasswordManager) -> None:
"""Set or clear the custom name for the current seed profile."""
fp = getattr(pm.fingerprint_manager, "current_fingerprint", None)
if not fp:
print(colored("No seed profile selected.", "red"))
return
current = pm.fingerprint_manager.get_name(fp)
if current:
print(colored(f"Current name: {current}", "cyan"))
else:
print(colored("No custom name set.", "cyan"))
value = input("Enter new name (leave blank to remove): ").strip()
if pm.fingerprint_manager.set_name(fp, value or None):
if value:
print(colored("Name updated.", "green"))
else:
print(colored("Name removed.", "green"))
def handle_toggle_secret_mode(pm: PasswordManager) -> None:
"""Toggle secret mode and adjust clipboard delay."""
cfg = pm.config_manager
@@ -756,6 +778,7 @@ def handle_profiles_menu(password_manager: PasswordManager) -> None:
print(color_text("2. Add a New Seed Profile", "menu"))
print(color_text("3. Remove an Existing Seed Profile", "menu"))
print(color_text("4. List All Seed Profiles", "menu"))
print(color_text("5. Set Seed Profile Name", "menu"))
choice = input("Select an option or press Enter to go back: ").strip()
password_manager.update_activity()
if choice == "1":
@@ -767,6 +790,8 @@ def handle_profiles_menu(password_manager: PasswordManager) -> None:
handle_remove_fingerprint(password_manager)
elif choice == "4":
handle_list_fingerprints(password_manager)
elif choice == "5":
handle_set_profile_name(password_manager)
elif not choice:
break
else:

View File

@@ -329,8 +329,13 @@ class PasswordManager:
print(colored("\nAvailable Seed Profiles:", "cyan"))
for idx, fp in enumerate(fingerprints, start=1):
label = (
self.fingerprint_manager.display_name(fp)
if hasattr(self.fingerprint_manager, "display_name")
else fp
)
marker = " *" if fp == current else ""
print(colored(f"{idx}. {fp}{marker}", "cyan"))
print(colored(f"{idx}. {label}{marker}", "cyan"))
print(colored(f"{len(fingerprints)+1}. Add a new seed profile", "cyan"))
@@ -532,7 +537,12 @@ class PasswordManager:
print(colored("\nAvailable Seed Profiles:", "cyan"))
fingerprints = self.fingerprint_manager.list_fingerprints()
for idx, fp in enumerate(fingerprints, start=1):
print(colored(f"{idx}. {fp}", "cyan"))
display = (
self.fingerprint_manager.display_name(fp)
if hasattr(self.fingerprint_manager, "display_name")
else fp
)
print(colored(f"{idx}. {display}", "cyan"))
choice = input("Select a seed profile by number to switch: ").strip()
if not choice.isdigit() or not (1 <= int(choice) <= len(fingerprints)):
@@ -680,7 +690,12 @@ class PasswordManager:
print(colored("Available Seed Profiles:", "cyan"))
for idx, fp in enumerate(fingerprints, start=1):
print(colored(f"{idx}. {fp}", "cyan"))
label = (
self.fingerprint_manager.display_name(fp)
if hasattr(self.fingerprint_manager, "display_name")
else fp
)
print(colored(f"{idx}. {label}", "cyan"))
choice = input("Select a seed profile by number: ").strip()
if not choice.isdigit() or not (1 <= int(choice) <= len(fingerprints)):

View File

@@ -34,7 +34,11 @@ class FingerprintManager:
self.app_dir = app_dir
self.fingerprints_file = self.app_dir / "fingerprints.json"
self._ensure_app_directory()
self.fingerprints, self.current_fingerprint = self._load_fingerprints()
(
self.fingerprints,
self.current_fingerprint,
self.names,
) = self._load_fingerprints()
def get_current_fingerprint_dir(self) -> Optional[Path]:
"""
@@ -62,25 +66,26 @@ class FingerprintManager:
)
raise
def _load_fingerprints(self) -> tuple[list[str], Optional[str]]:
"""Return stored fingerprints and the last used fingerprint."""
def _load_fingerprints(self) -> tuple[list[str], Optional[str], dict[str, str]]:
"""Return stored fingerprints, the last used fingerprint, and name mapping."""
try:
if self.fingerprints_file.exists():
with open(self.fingerprints_file, "r") as f:
data = json.load(f)
fingerprints = data.get("fingerprints", [])
current = data.get("last_used")
names = data.get("names", {})
logger.debug(
f"Loaded fingerprints: {fingerprints} (last used: {current})"
)
return fingerprints, current
return fingerprints, current, names
logger.debug(
"fingerprints.json not found. Initializing empty fingerprint list."
)
return [], None
return [], None, {}
except Exception as e:
logger.error(f"Failed to load fingerprints: {e}", exc_info=True)
return [], None
return [], None, {}
def _save_fingerprints(self):
"""
@@ -92,6 +97,7 @@ class FingerprintManager:
{
"fingerprints": self.fingerprints,
"last_used": self.current_fingerprint,
"names": self.names,
},
f,
indent=4,
@@ -116,6 +122,7 @@ class FingerprintManager:
fingerprint = generate_fingerprint(seed_phrase)
if fingerprint and fingerprint not in self.fingerprints:
self.fingerprints.append(fingerprint)
self.names.setdefault(fingerprint, "")
self.current_fingerprint = fingerprint
self._save_fingerprints()
logger.info(f"Fingerprint {fingerprint} added successfully.")
@@ -144,6 +151,7 @@ class FingerprintManager:
if fingerprint in self.fingerprints:
try:
self.fingerprints.remove(fingerprint)
self.names.pop(fingerprint, None)
if self.current_fingerprint == fingerprint:
self.current_fingerprint = (
self.fingerprints[0] if self.fingerprints else None
@@ -198,6 +206,26 @@ class FingerprintManager:
logger.error(f"Fingerprint {fingerprint} not found.")
return False
def set_name(self, fingerprint: str, name: str | None) -> bool:
"""Set a custom name for a fingerprint."""
if fingerprint not in self.fingerprints:
return False
if name:
self.names[fingerprint] = name
else:
self.names.pop(fingerprint, None)
self._save_fingerprints()
return True
def get_name(self, fingerprint: str) -> Optional[str]:
"""Return the custom name for ``fingerprint`` if set."""
return self.names.get(fingerprint) or None
def display_name(self, fingerprint: str) -> str:
"""Return name and fingerprint for display."""
name = self.get_name(fingerprint)
return f"{name} ({fingerprint})" if name else fingerprint
def get_fingerprint_directory(self, fingerprint: str) -> Optional[Path]:
"""
Retrieves the directory path for a given fingerprint.

View File

@@ -8,6 +8,20 @@ from termcolor import colored
from utils.color_scheme import color_text
def format_profile(fingerprint: str | None, pm=None) -> str | None:
"""Return display string for a fingerprint with optional custom name."""
if not fingerprint:
return None
if pm and getattr(pm, "fingerprint_manager", None):
try:
name = pm.fingerprint_manager.get_name(fingerprint)
if name:
return f"{name} ({fingerprint})"
except Exception:
pass
return fingerprint
def clear_screen() -> None:
"""Clear the terminal screen using an ANSI escape code."""
print("\033c", end="")
@@ -18,16 +32,17 @@ def clear_and_print_fingerprint(
breadcrumb: str | None = None,
parent_fingerprint: str | None = None,
child_fingerprint: str | None = None,
pm=None,
) -> None:
"""Clear the screen and optionally display the current fingerprint and path."""
clear_screen()
header_fp = None
if parent_fingerprint and child_fingerprint:
header_fp = f"{parent_fingerprint} > Managed Account > {child_fingerprint}"
header_fp = f"{format_profile(parent_fingerprint, pm)} > Managed Account > {format_profile(child_fingerprint, pm)}"
elif fingerprint:
header_fp = fingerprint
header_fp = format_profile(fingerprint, pm)
elif parent_fingerprint or child_fingerprint:
header_fp = parent_fingerprint or child_fingerprint
header_fp = format_profile(parent_fingerprint or child_fingerprint, pm)
if header_fp:
header = f"Seed Profile: {header_fp}"
if breadcrumb:
@@ -36,15 +51,15 @@ def clear_and_print_fingerprint(
def clear_and_print_profile_chain(
fingerprints: list[str] | None, breadcrumb: str | None = None
fingerprints: list[str] | None, breadcrumb: str | None = None, pm=None
) -> None:
"""Clear the screen and display a chain of fingerprints."""
clear_screen()
if not fingerprints:
return
chain = fingerprints[0]
chain = format_profile(fingerprints[0], pm)
for fp in fingerprints[1:]:
chain += f" > Managed Account > {fp}"
chain += f" > Managed Account > {format_profile(fp, pm)}"
header = f"Seed Profile: {chain}"
if breadcrumb:
header += f" > {breadcrumb}"
@@ -63,11 +78,11 @@ def clear_header_with_notification(
clear_screen()
header_fp = None
if parent_fingerprint and child_fingerprint:
header_fp = f"{parent_fingerprint} > Managed Account > {child_fingerprint}"
header_fp = f"{format_profile(parent_fingerprint, pm)} > Managed Account > {format_profile(child_fingerprint, pm)}"
elif fingerprint:
header_fp = fingerprint
header_fp = format_profile(fingerprint, pm)
elif parent_fingerprint or child_fingerprint:
header_fp = parent_fingerprint or child_fingerprint
header_fp = format_profile(parent_fingerprint or child_fingerprint, pm)
if header_fp:
header = f"Seed Profile: {header_fp}"
if breadcrumb: