Add color scheme helper and apply to menus and stats

This commit is contained in:
thePR0M3TH3AN
2025-07-05 19:06:46 -04:00
parent dd59a0e61b
commit e7d6b7d46e
3 changed files with 157 additions and 104 deletions

View File

@@ -12,6 +12,7 @@ import gzip
import tomli import tomli
from colorama import init as colorama_init from colorama import init as colorama_init
from termcolor import colored from termcolor import colored
from utils.color_scheme import color_text
import traceback import traceback
from password_manager.manager import PasswordManager from password_manager.manager import PasswordManager
@@ -248,26 +249,28 @@ def print_matches(
if data if data
else EntryType.PASSWORD.value else EntryType.PASSWORD.value
) )
print(colored(f"Index: {idx}", "cyan")) print(color_text(f"Index: {idx}", "index"))
if etype == EntryType.TOTP.value: if etype == EntryType.TOTP.value:
print(colored(f" Label: {data.get('label', website)}", "cyan")) print(color_text(f" Label: {data.get('label', website)}", "index"))
print(colored(f" Derivation Index: {data.get('index', idx)}", "cyan")) print(color_text(f" Derivation Index: {data.get('index', idx)}", "index"))
elif etype == EntryType.SEED.value: elif etype == EntryType.SEED.value:
print(colored(" Type: Seed Phrase", "cyan")) print(color_text(" Type: Seed Phrase", "index"))
elif etype == EntryType.SSH.value: elif etype == EntryType.SSH.value:
print(colored(" Type: SSH Key", "cyan")) print(color_text(" Type: SSH Key", "index"))
elif etype == EntryType.PGP.value: elif etype == EntryType.PGP.value:
print(colored(" Type: PGP Key", "cyan")) print(color_text(" Type: PGP Key", "index"))
elif etype == EntryType.NOSTR.value: elif etype == EntryType.NOSTR.value:
print(colored(" Type: Nostr Key", "cyan")) print(color_text(" Type: Nostr Key", "index"))
else: else:
if website: if website:
print(colored(f" Label: {website}", "cyan")) print(color_text(f" Label: {website}", "index"))
if username: if username:
print(colored(f" Username: {username}", "cyan")) print(color_text(f" Username: {username}", "index"))
if url: if url:
print(colored(f" URL: {url}", "cyan")) print(color_text(f" URL: {url}", "index"))
print(colored(f" Blacklisted: {'Yes' if blacklisted else 'No'}", "cyan")) print(
color_text(f" Blacklisted: {'Yes' if blacklisted else 'No'}", "index")
)
print("-" * 40) print("-" * 40)
@@ -565,12 +568,12 @@ def handle_toggle_secret_mode(pm: PasswordManager) -> None:
def handle_profiles_menu(password_manager: PasswordManager) -> None: def handle_profiles_menu(password_manager: PasswordManager) -> None:
"""Submenu for managing seed profiles.""" """Submenu for managing seed profiles."""
while True: while True:
print("\nProfiles:") print(color_text("\nProfiles:", "menu"))
print("1. Switch Seed Profile") print(color_text("1. Switch Seed Profile", "menu"))
print("2. Add a New Seed Profile") print(color_text("2. Add a New Seed Profile", "menu"))
print("3. Remove an Existing Seed Profile") print(color_text("3. Remove an Existing Seed Profile", "menu"))
print("4. List All Seed Profiles") print(color_text("4. List All Seed Profiles", "menu"))
print("5. Back") print(color_text("5. Back", "menu"))
choice = input("Select an option: ").strip() choice = input("Select an option: ").strip()
password_manager.update_activity() password_manager.update_activity()
if choice == "1": if choice == "1":
@@ -601,15 +604,15 @@ def handle_nostr_menu(password_manager: PasswordManager) -> None:
return return
while True: while True:
print("\nNostr Settings:") print(color_text("\nNostr Settings:", "menu"))
print("1. Backup to Nostr") print(color_text("1. Backup to Nostr", "menu"))
print("2. Restore from Nostr") print(color_text("2. Restore from Nostr", "menu"))
print("3. View current relays") print(color_text("3. View current relays", "menu"))
print("4. Add a relay URL") print(color_text("4. Add a relay URL", "menu"))
print("5. Remove a relay by number") print(color_text("5. Remove a relay by number", "menu"))
print("6. Reset to default relays") print(color_text("6. Reset to default relays", "menu"))
print("7. Display Nostr Public Key") print(color_text("7. Display Nostr Public Key", "menu"))
print("8. Back") print(color_text("8. Back", "menu"))
choice = input("Select an option: ").strip() choice = input("Select an option: ").strip()
password_manager.update_activity() password_manager.update_activity()
if choice == "1": if choice == "1":
@@ -635,22 +638,22 @@ def handle_nostr_menu(password_manager: PasswordManager) -> None:
def handle_settings(password_manager: PasswordManager) -> None: def handle_settings(password_manager: PasswordManager) -> None:
"""Interactive settings menu with submenus for profiles and Nostr.""" """Interactive settings menu with submenus for profiles and Nostr."""
while True: while True:
print("\nSettings:") print(color_text("\nSettings:", "menu"))
print("1. Profiles") print(color_text("1. Profiles", "menu"))
print("2. Nostr") print(color_text("2. Nostr", "menu"))
print("3. Change password") print(color_text("3. Change password", "menu"))
print("4. Verify Script Checksum") print(color_text("4. Verify Script Checksum", "menu"))
print("5. Generate Script Checksum") print(color_text("5. Generate Script Checksum", "menu"))
print("6. Backup Parent Seed") print(color_text("6. Backup Parent Seed", "menu"))
print("7. Export database") print(color_text("7. Export database", "menu"))
print("8. Import database") print(color_text("8. Import database", "menu"))
print("9. Export 2FA codes") print(color_text("9. Export 2FA codes", "menu"))
print("10. Set additional backup location") print(color_text("10. Set additional backup location", "menu"))
print("11. Set inactivity timeout") print(color_text("11. Set inactivity timeout", "menu"))
print("12. Lock Vault") print(color_text("12. Lock Vault", "menu"))
print("13. Stats") print(color_text("13. Stats", "menu"))
print("14. Toggle Secret Mode") print(color_text("14. Toggle Secret Mode", "menu"))
print("15. Back") print(color_text("15. Back", "menu"))
choice = input("Select an option: ").strip() choice = input("Select an option: ").strip()
if choice == "1": if choice == "1":
handle_profiles_menu(password_manager) handle_profiles_menu(password_manager)
@@ -729,7 +732,7 @@ def display_menu(
# Flush logging handlers # Flush logging handlers
for handler in logging.getLogger().handlers: for handler in logging.getLogger().handlers:
handler.flush() handler.flush()
print(colored(menu, "cyan")) print(color_text(menu, "menu"))
try: try:
choice = timed_input( choice = timed_input(
"Enter your choice (1-8): ", inactivity_timeout "Enter your choice (1-8): ", inactivity_timeout
@@ -750,14 +753,14 @@ def display_menu(
continue # Re-display the menu without marking as invalid continue # Re-display the menu without marking as invalid
if choice == "1": if choice == "1":
while True: while True:
print("\nAdd Entry:") print(color_text("\nAdd Entry:", "menu"))
print("1. Password") print(color_text("1. Password", "menu"))
print("2. 2FA (TOTP)") print(color_text("2. 2FA (TOTP)", "menu"))
print("3. SSH Key") print(color_text("3. SSH Key", "menu"))
print("4. Seed Phrase") print(color_text("4. Seed Phrase", "menu"))
print("5. Nostr Key Pair") print(color_text("5. Nostr Key Pair", "menu"))
print("6. PGP Key") print(color_text("6. PGP Key", "menu"))
print("7. Back") print(color_text("7. Back", "menu"))
sub_choice = input("Select entry type: ").strip() sub_choice = input("Select entry type: ").strip()
password_manager.update_activity() password_manager.update_activity()
if sub_choice == "1": if sub_choice == "1":

View File

@@ -20,6 +20,7 @@ import shutil
import time import time
import builtins import builtins
from termcolor import colored from termcolor import colored
from utils.color_scheme import color_text
from utils.input_utils import timed_input from utils.input_utils import timed_input
from password_manager.encryption import EncryptionManager from password_manager.encryption import EncryptionManager
@@ -1832,76 +1833,90 @@ class PasswordManager:
return return
etype = entry.get("type", entry.get("kind", EntryType.PASSWORD.value)) etype = entry.get("type", entry.get("kind", EntryType.PASSWORD.value))
print(colored(f"Index: {index}", "cyan")) print(color_text(f"Index: {index}", "index"))
if etype == EntryType.TOTP.value: if etype == EntryType.TOTP.value:
print(colored(f" Label: {entry.get('label', '')}", "cyan")) print(color_text(f" Label: {entry.get('label', '')}", "index"))
print(colored(f" Derivation Index: {entry.get('index', index)}", "cyan"))
print( print(
colored( color_text(f" Derivation Index: {entry.get('index', index)}", "index")
)
print(
color_text(
f" Period: {entry.get('period', 30)}s Digits: {entry.get('digits', 6)}", f" Period: {entry.get('period', 30)}s Digits: {entry.get('digits', 6)}",
"cyan", "index",
) )
) )
notes = entry.get("notes", "") notes = entry.get("notes", "")
if notes: if notes:
print(colored(f" Notes: {notes}", "cyan")) print(color_text(f" Notes: {notes}", "index"))
elif etype == EntryType.SEED.value: elif etype == EntryType.SEED.value:
print(colored(" Type: Seed Phrase", "cyan")) print(color_text(" Type: Seed Phrase", "index"))
print(colored(f" Label: {entry.get('label', '')}", "cyan")) print(color_text(f" Label: {entry.get('label', '')}", "index"))
print(colored(f" Words: {entry.get('words', 24)}", "cyan")) print(color_text(f" Words: {entry.get('words', 24)}", "index"))
print(colored(f" Derivation Index: {entry.get('index', index)}", "cyan")) print(
color_text(f" Derivation Index: {entry.get('index', index)}", "index")
)
notes = entry.get("notes", "") notes = entry.get("notes", "")
if notes: if notes:
print(colored(f" Notes: {notes}", "cyan")) print(color_text(f" Notes: {notes}", "index"))
elif etype == EntryType.SSH.value: elif etype == EntryType.SSH.value:
print(colored(" Type: SSH Key", "cyan")) print(color_text(" Type: SSH Key", "index"))
print(colored(f" Label: {entry.get('label', '')}", "cyan")) print(color_text(f" Label: {entry.get('label', '')}", "index"))
print(colored(f" Derivation Index: {entry.get('index', index)}", "cyan")) print(
color_text(f" Derivation Index: {entry.get('index', index)}", "index")
)
notes = entry.get("notes", "") notes = entry.get("notes", "")
if notes: if notes:
print(colored(f" Notes: {notes}", "cyan")) print(color_text(f" Notes: {notes}", "index"))
elif etype == EntryType.PGP.value: elif etype == EntryType.PGP.value:
print(colored(" Type: PGP Key", "cyan")) print(color_text(" Type: PGP Key", "index"))
print(colored(f" Label: {entry.get('label', '')}", "cyan")) print(color_text(f" Label: {entry.get('label', '')}", "index"))
print(colored(f" Key Type: {entry.get('key_type', 'ed25519')}", "cyan")) print(
color_text(f" Key Type: {entry.get('key_type', 'ed25519')}", "index")
)
uid = entry.get("user_id", "") uid = entry.get("user_id", "")
if uid: if uid:
print(colored(f" User ID: {uid}", "cyan")) print(color_text(f" User ID: {uid}", "index"))
print(colored(f" Derivation Index: {entry.get('index', index)}", "cyan")) print(
color_text(f" Derivation Index: {entry.get('index', index)}", "index")
)
notes = entry.get("notes", "") notes = entry.get("notes", "")
if notes: if notes:
print(colored(f" Notes: {notes}", "cyan")) print(color_text(f" Notes: {notes}", "index"))
elif etype == EntryType.NOSTR.value: elif etype == EntryType.NOSTR.value:
print(colored(" Type: Nostr Key", "cyan")) print(color_text(" Type: Nostr Key", "index"))
print(colored(f" Label: {entry.get('label', '')}", "cyan")) print(color_text(f" Label: {entry.get('label', '')}", "index"))
print(colored(f" Derivation Index: {entry.get('index', index)}", "cyan")) print(
color_text(f" Derivation Index: {entry.get('index', index)}", "index")
)
notes = entry.get("notes", "") notes = entry.get("notes", "")
if notes: if notes:
print(colored(f" Notes: {notes}", "cyan")) print(color_text(f" Notes: {notes}", "index"))
else: else:
website = entry.get("label", entry.get("website", "")) website = entry.get("label", entry.get("website", ""))
username = entry.get("username", "") username = entry.get("username", "")
url = entry.get("url", "") url = entry.get("url", "")
blacklisted = entry.get("blacklisted", False) blacklisted = entry.get("blacklisted", False)
print(colored(f" Label: {website}", "cyan")) print(color_text(f" Label: {website}", "index"))
print(colored(f" Username: {username or 'N/A'}", "cyan")) print(color_text(f" Username: {username or 'N/A'}", "index"))
print(colored(f" URL: {url or 'N/A'}", "cyan")) print(color_text(f" URL: {url or 'N/A'}", "index"))
print(colored(f" Blacklisted: {'Yes' if blacklisted else 'No'}", "cyan")) print(
color_text(f" Blacklisted: {'Yes' if blacklisted else 'No'}", "index")
)
print("-" * 40) print("-" * 40)
def handle_list_entries(self) -> None: def handle_list_entries(self) -> None:
"""List entries and optionally show details.""" """List entries and optionally show details."""
try: try:
while True: while True:
print("\nList Entries:") print(color_text("\nList Entries:", "menu"))
print("1. All") print(color_text("1. All", "menu"))
print("2. Passwords") print(color_text("2. Passwords", "menu"))
print("3. 2FA (TOTP)") print(color_text("3. 2FA (TOTP)", "menu"))
print("4. SSH Key") print(color_text("4. SSH Key", "menu"))
print("5. Seed Phrase") print(color_text("5. Seed Phrase", "menu"))
print("6. Nostr Key Pair") print(color_text("6. Nostr Key Pair", "menu"))
print("7. PGP") print(color_text("7. PGP", "menu"))
print("8. Back") print(color_text("8. Back", "menu"))
choice = input("Select entry type: ").strip() choice = input("Select entry type: ").strip()
if choice == "1": if choice == "1":
filter_kind = None filter_kind = None
@@ -2602,34 +2617,37 @@ class PasswordManager:
print(colored("No statistics available.", "red")) print(colored("No statistics available.", "red"))
return return
print(colored("\n=== Seed Profile Stats ===", "yellow")) print(color_text("\n=== Seed Profile Stats ===", "stats"))
print(colored(f"Total entries: {stats['total_entries']}", "cyan")) print(color_text(f"Total entries: {stats['total_entries']}", "stats"))
for etype, count in stats["entries"].items(): for etype, count in stats["entries"].items():
print(colored(f" {etype}: {count}", "cyan")) print(color_text(f" {etype}: {count}", "stats"))
print(colored(f"Relays configured: {stats['relay_count']}", "cyan")) print(color_text(f"Relays configured: {stats['relay_count']}", "stats"))
print( print(
colored( color_text(
f"Backups: {stats['backup_count']} (dir: {stats['backup_dir']})", "cyan" f"Backups: {stats['backup_count']} (dir: {stats['backup_dir']})",
"stats",
) )
) )
if stats.get("additional_backup_path"): if stats.get("additional_backup_path"):
print( print(
colored(f"Additional backup: {stats['additional_backup_path']}", "cyan") color_text(
f"Additional backup: {stats['additional_backup_path']}", "stats"
)
) )
print(colored(f"Schema version: {stats['schema_version']}", "cyan")) print(color_text(f"Schema version: {stats['schema_version']}", "stats"))
print( print(
colored( color_text(
f"Database checksum ok: {'yes' if stats['checksum_ok'] else 'no'}", f"Database checksum ok: {'yes' if stats['checksum_ok'] else 'no'}",
"cyan", "stats",
) )
) )
print( print(
colored( color_text(
f"Script checksum ok: {'yes' if stats['script_checksum_ok'] else 'no'}", f"Script checksum ok: {'yes' if stats['script_checksum_ok'] else 'no'}",
"cyan", "stats",
) )
) )
print(colored(f"Snapshot chunks: {stats['chunk_count']}", "cyan")) print(color_text(f"Snapshot chunks: {stats['chunk_count']}", "stats"))
print(colored(f"Pending deltas: {stats['pending_deltas']}", "cyan")) print(color_text(f"Pending deltas: {stats['pending_deltas']}", "stats"))
if stats.get("delta_since"): if stats.get("delta_since"):
print(colored(f"Latest delta id: {stats['delta_since']}", "cyan")) print(color_text(f"Latest delta id: {stats['delta_since']}", "stats"))

32
src/utils/color_scheme.py Normal file
View File

@@ -0,0 +1,32 @@
"""Utility functions for SeedPass CLI color scheme."""
from termcolor import colored
# ANSI escape for 256-color orange (color code 208)
_ORANGE = "\033[38;5;208m"
_RESET = "\033[0m"
def _apply_orange(text: str) -> str:
"""Return text wrapped in ANSI codes for orange."""
return f"{_ORANGE}{text}{_RESET}"
# Mapping of semantic color categories to actual colors
_COLOR_MAP = {
"deterministic": "red",
"imported": "orange",
"index": "yellow",
"menu": "blue",
"stats": "green",
"default": "white",
}
def color_text(text: str, category: str = "default") -> str:
"""Colorize ``text`` according to the given category."""
color = _COLOR_MAP.get(category, "white")
if color == "orange":
return _apply_orange(text)
return colored(text, color)