diff --git a/.jules/palette.md b/.jules/palette.md index fbedb303..7be86fd3 100644 --- a/.jules/palette.md +++ b/.jules/palette.md @@ -14,3 +14,11 @@ ## 2024-03-22 - CLI Interactive Fallbacks **Learning:** CLI tools often fail hard when config is missing, but interactive contexts allow for graceful recovery. Users appreciate being asked for missing info instead of just receiving an error. **Action:** When `sys.stdin.isatty()` is true, prompt for missing configuration instead of exiting with an error code. + +## 2025-05-24 - CLI Accessibility Standards +**Learning:** CLI tools often lack standard accessibility features like `NO_COLOR` support, assuming TTY checks are enough. However, users may want to disable colors even in TTYs for contrast reasons. +**Action:** Always check `os.getenv("NO_COLOR")` in CLI tools alongside TTY checks to respect user preference. + +## 2025-05-24 - Reducing CLI Log Noise +**Learning:** High-volume repetitive logs (like batch processing) drown out important errors and context. +**Action:** Use single-line overwriting updates (`\r`) for repetitive progress in interactive sessions, falling back to standard logging for non-interactive streams. diff --git a/main.py b/main.py index e6aabc57..59c52bde 100644 --- a/main.py +++ b/main.py @@ -35,7 +35,11 @@ load_dotenv() # Determine if we should use colors -USE_COLORS = sys.stderr.isatty() and sys.stdout.isatty() +# Respect NO_COLOR standard (https://no-color.org/) +if os.getenv("NO_COLOR"): + USE_COLORS = False +else: + USE_COLORS = sys.stderr.isatty() and sys.stdout.isatty() class Colors: if USE_COLORS: @@ -504,10 +508,16 @@ def push_rules( try: _api_post_form(client, f"{API_BASE}/{profile_id}/rules", data=data) - log.info( - "Folder %s – batch %d: added %d rules", - sanitize_for_log(folder_name), i, len(batch) - ) + + if USE_COLORS: + sys.stderr.write(f"\r{Colors.CYAN}🚀 Folder {sanitize_for_log(folder_name)}: Pushing batch {i}/{total_batches}...{Colors.ENDC}") + sys.stderr.flush() + else: + log.info( + "Folder %s – batch %d: added %d rules", + sanitize_for_log(folder_name), i, len(batch) + ) + successful_batches += 1 if existing_rules_lock: with existing_rules_lock: @@ -515,12 +525,18 @@ def push_rules( else: existing_rules.update(batch) except httpx.HTTPError as e: + if USE_COLORS: + sys.stderr.write("\n") log.error(f"Failed to push batch {i} for folder {sanitize_for_log(folder_name)}: {sanitize_for_log(e)}") if hasattr(e, 'response') and e.response is not None: log.debug(f"Response content: {e.response.text}") if successful_batches == total_batches: - log.info("Folder %s – finished (%d new rules added)", sanitize_for_log(folder_name), len(filtered_hostnames)) + if USE_COLORS: + sys.stderr.write(f"\r{Colors.GREEN}✅ Folder {sanitize_for_log(folder_name)}: Finished ({len(filtered_hostnames)} rules) {Colors.ENDC}\n") + sys.stderr.flush() + else: + log.info("Folder %s – finished (%d new rules added)", sanitize_for_log(folder_name), len(filtered_hostnames)) return True else: log.error("Folder %s – only %d/%d batches succeeded", sanitize_for_log(folder_name), successful_batches, total_batches)