diff --git a/.Jules/palette.md b/.Jules/palette.md index c11e5241..536b226e 100644 --- a/.Jules/palette.md +++ b/.Jules/palette.md @@ -5,3 +5,7 @@ ## 2025-02-14 - [ASCII Fallback for Tables] **Learning:** Using Unicode box drawing characters enhances the CLI experience, but a robust ASCII fallback is crucial for CI environments and piped outputs. **Action:** Always implement a fallback mechanism (like checking `sys.stderr.isatty()`) when using rich text or Unicode symbols. + +## 2025-02-28 - [Interactive Restart] +**Learning:** Reconstructing command arguments manually for process restarts is brittle and breaks forward compatibility. +**Action:** When restarting a CLI tool with modified flags (e.g., removing `--dry-run`), filter `sys.argv` instead of rebuilding the argument list from parsed args. diff --git a/main.py b/main.py index c835ff1b..3d365370 100644 --- a/main.py +++ b/main.py @@ -2355,6 +2355,53 @@ def _fetch_if_valid(url: str): # --------------------------------------------------------------------------- # # 5. Entry-point # --------------------------------------------------------------------------- # +def prompt_for_interactive_restart(profile_ids: List[str]) -> None: + """ + Prompts the user to restart the script in live mode (after a successful dry run). + + If the user confirms, the script restarts itself using os.execv, preserving + all original arguments (except --dry-run) and environment variables. + + This function only runs if sys.stdin is a TTY (interactive session). + """ + if not sys.stdin.isatty(): + return + + try: + if USE_COLORS: + prompt = f"\n{Colors.BOLD}šŸš€ Ready to launch? {Colors.ENDC}Press [Enter] to run now (or Ctrl+C to cancel)..." + else: + prompt = "\nšŸš€ Ready to launch? Press [Enter] to run now (or Ctrl+C to cancel)..." + + # Flush stderr to ensure prompt is visible + sys.stderr.flush() + input(prompt) + + # Prepare environment for the new process + # Pass the current token to avoid re-prompting if it was entered interactively + if TOKEN: + os.environ["TOKEN"] = TOKEN + + # Construct command arguments + # Use sys.argv filtering to preserve all user-provided flags (even future ones) + # while removing --dry-run to switch to live mode. + clean_argv = [arg for arg in sys.argv[1:] if arg != "--dry-run"] + new_argv = [sys.executable, sys.argv[0]] + clean_argv + + # If --profiles wasn't in original args (meaning it came from env/input), + # inject it explicitly so the user doesn't have to re-enter it. + if "--profiles" not in sys.argv and profile_ids: + new_argv.extend(["--profiles", ",".join(profile_ids)]) + + print(f"\n{Colors.GREEN}šŸ”„ Restarting in live mode...{Colors.ENDC}") + # Security: The input to execv is derived from trusted sys.argv and validated profile_ids. + # It restarts the same script with the same python interpreter. + os.execv(sys.executable, new_argv) # nosec B606 + + except (KeyboardInterrupt, EOFError): + print(f"\n{Colors.WARNING}āš ļø Cancelled.{Colors.ENDC}") + + def print_summary_table( sync_results: List[Dict[str, Any]], success_count: int, total: int, dry_run: bool ) -> None: @@ -2758,6 +2805,10 @@ def make_col_separator(left, mid, right, horiz): else: print("šŸ‘‰ Ready to sync? Run the following command:") print(f" {cmd_str}") + + # Offer interactive restart if appropriate + prompt_for_interactive_restart(profile_ids) + else: if USE_COLORS: print(