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

View File

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