Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/palette.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2024-05-21 - CLI Cursor Hygiene
**Learning:** Hiding the terminal cursor (`\033[?25l`) during progress bar updates eliminates flickering and looks more professional.
**Action:** Always use `atexit` to register a cleanup function that restores the cursor (`\033[?25h`) to prevent leaving the user's terminal in a broken state if the script crashes.
40 changes: 18 additions & 22 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"""

import argparse
import atexit
import concurrent.futures
import getpass
import ipaddress
Expand Down Expand Up @@ -56,6 +57,8 @@
ENDC = "\033[0m"
BOLD = "\033[1m"
UNDERLINE = "\033[4m"
CURSOR_HIDE = "\033[?25l"
CURSOR_SHOW = "\033[?25h"
else:
HEADER = ""
BLUE = ""
Expand All @@ -66,6 +69,8 @@
ENDC = ""
BOLD = ""
UNDERLINE = ""
CURSOR_HIDE = ""
CURSOR_SHOW = ""


class ColoredFormatter(logging.Formatter):
Expand Down Expand Up @@ -102,6 +107,16 @@
logging.getLogger("httpx").setLevel(logging.WARNING)


def restore_cursor():
"""Ensure cursor is visible when the script exits."""
if USE_COLORS:
sys.stderr.write(Colors.CURSOR_SHOW)
sys.stderr.flush()
Comment on lines +113 to +114

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To make the atexit handler more robust, consider wrapping the write operation in a try...except block. In the edge case where sys.stderr is closed or otherwise unavailable when this handler runs, sys.stderr.write() would raise an exception, leading to a traceback on exit. This change will prevent that and ensure a cleaner exit.

Suggested change
sys.stderr.write(Colors.CURSOR_SHOW)
sys.stderr.flush()
try:
sys.stderr.write(Colors.CURSOR_SHOW)
sys.stderr.flush()
except Exception:
# stderr might be closed, nothing we can do.
pass



atexit.register(restore_cursor)
Comment on lines +110 to +117
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new restore_cursor function and its atexit registration lack test coverage. Consider adding a test that verifies the function writes CURSOR_SHOW to stderr when USE_COLORS is True, and verifies the atexit registration. This is important for ensuring the cursor restoration mechanism works correctly.

Copilot uses AI. Check for mistakes.


def check_env_permissions(env_path: str = ".env") -> None:
"""
Check .env file permissions and warn if readable by others.
Expand Down Expand Up @@ -159,25 +174,6 @@
return safe


def render_progress_bar(
current: int, total: int, label: str, prefix: str = "🚀"
) -> None:
if not USE_COLORS or total == 0:
return

width = 20
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.flush()


def countdown_timer(seconds: int, message: str = "Waiting") -> None:
"""Shows a countdown timer if strictly in a TTY, otherwise just sleeps."""
if not USE_COLORS:
Expand All @@ -190,7 +186,7 @@
filled = int(width * progress)
bar = "█" * filled + "░" * (width - filled)
sys.stderr.write(
f"\r{Colors.CYAN}⏳ {message}: [{bar}] {remaining}s...{Colors.ENDC}"
f"\r{Colors.CURSOR_HIDE}{Colors.CYAN}⏳ {message}: [{bar}] {remaining}s...{Colors.ENDC}"
)
sys.stderr.flush()
time.sleep(1)
Comment on lines +189 to 192
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The countdown_timer function now includes cursor hiding (CURSOR_HIDE on line 189), but there's no test coverage for this function in test_main.py. Since render_progress_bar has test coverage, countdown_timer should also have tests to verify the cursor hiding behavior and the countdown functionality.

Copilot uses AI. Check for mistakes.
Comment on lines +189 to 192
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The countdown_timer function hides the cursor on line 189 but doesn't explicitly restore it when it completes on lines 194-195. While the atexit handler will restore it eventually, it's better to restore the cursor immediately after the timer finishes to avoid leaving it hidden longer than necessary. Consider adding the cursor restoration code before this line.

Copilot uses AI. Check for mistakes.
Expand All @@ -206,15 +202,15 @@
if not USE_COLORS or total == 0:
return

width = 15
width = 30
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}"
f"\r{Colors.CURSOR_HIDE}\033[K{Colors.CYAN}{prefix} {label}: [{bar}] {percent}% ({current}/{total}){Colors.ENDC}"

Check warning

Code scanning / Pylint (reported by Codacy)

Line too long (121/100) Warning

Line too long (121/100)

Check warning

Code scanning / Pylintpython3 (reported by Codacy)

Line too long (121/100) Warning

Line too long (121/100)
)
sys.stderr.flush()

Comment on lines +213 to 216
Copy link

Copilot AI Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The render_progress_bar function now includes cursor hiding functionality (CURSOR_HIDE), but the existing test in test_main.py:491-512 doesn't verify this new escape sequence is present in the output. Consider updating the test to assert that the CURSOR_HIDE sequence is included when USE_COLORS is enabled.

Copilot uses AI. Check for mistakes.
Expand Down
Loading