From 1fa4e1c9aabfb51cec8b1b8eefce16e00cb7efe1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 22:52:13 +0000 Subject: [PATCH 1/2] feat(ux): enhance CLI feedback and visuals - Improved validation feedback for Profile ID prompts: users now see specific error reasons (e.g., invalid characters) instead of a generic message. - Standardized progress bar width to 30 characters for better visibility. - Removed duplicate `render_progress_bar` function definition. - Updated completion message to "Complete!". - Added journal entry in `.jules/palette.md`. Co-authored-by: abhimehro <84992105+abhimehro@users.noreply.github.com> --- .jules/palette.md | 3 +++ main.py | 37 ++++++++++--------------------------- tests/test_ux.py | 2 +- 3 files changed, 14 insertions(+), 28 deletions(-) create mode 100644 .jules/palette.md diff --git a/.jules/palette.md b/.jules/palette.md new file mode 100644 index 00000000..8bb5c48e --- /dev/null +++ b/.jules/palette.md @@ -0,0 +1,3 @@ +## 2026-01-29 - CLI UX Patterns +**Learning:** CLI tools often suffer from "duplicate feedback" where a validator logs an error AND the input loop prints a generic error. +**Action:** Silence the generic error if the validator provides specific feedback, or direct the user to the specific errors. diff --git a/main.py b/main.py index 86792da4..459f2187 100644 --- a/main.py +++ b/main.py @@ -162,10 +162,11 @@ def sanitize_for_log(text: Any) -> str: def render_progress_bar( current: int, total: int, label: str, prefix: str = "🚀" ) -> None: + """Renders a progress bar to stderr if USE_COLORS is True.""" if not USE_COLORS or total == 0: return - width = 20 + width = 30 progress = min(1.0, current / total) filled = int(width * progress) bar = "█" * filled + "░" * (width - filled) @@ -184,7 +185,7 @@ def countdown_timer(seconds: int, message: str = "Waiting") -> None: time.sleep(seconds) return - width = 15 + width = 30 for remaining in range(seconds, 0, -1): progress = (seconds - remaining + 1) / seconds filled = int(width * progress) @@ -195,27 +196,7 @@ def countdown_timer(seconds: int, message: str = "Waiting") -> None: sys.stderr.flush() time.sleep(1) - sys.stderr.write(f"\r\033[K{Colors.GREEN}✅ {message}: Done!{Colors.ENDC}\n") - sys.stderr.flush() - - -def render_progress_bar( - current: int, total: int, label: str, prefix: str = "🚀" -) -> None: - """Renders a progress bar to stderr if USE_COLORS is True.""" - if not USE_COLORS or total == 0: - return - - width = 15 - progress = min(1.0, current / total) - filled = int(width * progress) - bar = "█" * filled + "░" * (width - filled) - percent = int(progress * 100) - - # Use \033[K to clear line residue - sys.stderr.write( - f"\r\033[K{Colors.CYAN}{prefix} {label}: [{bar}] {percent}% ({current}/{total}){Colors.ENDC}" - ) + sys.stderr.write(f"\r\033[K{Colors.GREEN}✅ {message}: Complete!{Colors.ENDC}\n") sys.stderr.flush() @@ -409,9 +390,11 @@ def validate_profile_id(profile_id: str, log_errors: bool = True) -> bool: if not is_valid_profile_id_format(profile_id): if log_errors: if not re.match(r"^[a-zA-Z0-9_-]+$", profile_id): - log.error("Invalid profile ID format (contains unsafe characters)") + log.error( + f"Invalid profile ID '{profile_id}': contains unsafe characters (allowed: A-Z, a-z, 0-9, _, -)" + ) elif len(profile_id) > 64: - log.error("Invalid profile ID length (max 64 chars)") + log.error(f"Invalid profile ID '{profile_id}': too long (max 64 chars)") return False return True @@ -1249,13 +1232,13 @@ def main(): def validate_profile_input(value: str) -> bool: ids = [extract_profile_id(p) for p in value.split(",") if p.strip()] return bool(ids) and all( - validate_profile_id(pid, log_errors=False) for pid in ids + validate_profile_id(pid, log_errors=True) for pid in ids ) p_input = get_validated_input( f"{Colors.BOLD}Enter Control D Profile ID:{Colors.ENDC} ", validate_profile_input, - "Invalid ID(s) or URL(s). Must be a valid Profile ID or a Control D Profile URL. Comma-separate for multiple.", + "Please enter valid Profile ID(s). See errors above.", ) profile_ids = [ extract_profile_id(p) for p in p_input.split(",") if p.strip() diff --git a/tests/test_ux.py b/tests/test_ux.py index 311b7e91..c626071e 100644 --- a/tests/test_ux.py +++ b/tests/test_ux.py @@ -25,7 +25,7 @@ def test_countdown_timer_visuals(monkeypatch): assert "░" in combined_output assert "█" in combined_output assert "Test" in combined_output - assert "Done!" in combined_output + assert "Complete!" in combined_output # Check for ANSI clear line code assert "\033[K" in combined_output From 95dcbc209f15adc6f5c76ad784ad3c526ea741a7 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 29 Jan 2026 22:55:35 +0000 Subject: [PATCH 2/2] fix(security): sanitize profile ID in error logs - Applied `sanitize_for_log` to user inputs in `validate_profile_id` logs. - Fixes CodeQL alert for potential cleartext logging of sensitive data. - Maintains improved UX feedback for validation errors. Co-authored-by: abhimehro <84992105+abhimehro@users.noreply.github.com> --- main.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 459f2187..3f6bab79 100644 --- a/main.py +++ b/main.py @@ -391,10 +391,12 @@ def validate_profile_id(profile_id: str, log_errors: bool = True) -> bool: if log_errors: if not re.match(r"^[a-zA-Z0-9_-]+$", profile_id): log.error( - f"Invalid profile ID '{profile_id}': contains unsafe characters (allowed: A-Z, a-z, 0-9, _, -)" + f"Invalid profile ID {sanitize_for_log(profile_id)}: contains unsafe characters (allowed: A-Z, a-z, 0-9, _, -)" ) elif len(profile_id) > 64: - log.error(f"Invalid profile ID '{profile_id}': too long (max 64 chars)") + log.error( + f"Invalid profile ID {sanitize_for_log(profile_id)}: too long (max 64 chars)" + ) return False return True