@@ -2451,7 +2451,26 @@ def _first_time_setup(self, interactive=True) -> Dict:
24512451 """
24522452 Interactive setup that keeps the native Python in its original location
24532453 and only manages additional Python versions.
2454- """
2454+
2455+ AUTO-DETECTS non-interactive environments (Docker, CI, piped input, etc.)
2456+ """
2457+ import sys
2458+
2459+ # ============================================================================
2460+ # CRITICAL: Auto-detect non-interactive environments
2461+ # ============================================================================
2462+ is_docker = os.path.exists("/.dockerenv") or os.path.exists("/run/.containerenv")
2463+ no_tty = not sys.stdin.isatty()
2464+ forced_noninteractive = os.environ.get("OMNIPKG_NONINTERACTIVE")
2465+ in_ci = os.environ.get("CI")
2466+
2467+ # If ANY of these conditions are true, force non-interactive mode
2468+ if interactive and (in_ci or forced_noninteractive or no_tty or is_docker):
2469+ interactive = False
2470+ safe_print(_("🤖 Non-interactive environment detected - using defaults"))
2471+
2472+ # ============================================================================
2473+
24552474 safe_print(_("💡 Grounding configuration in the current active environment..."))
24562475
24572476 # Use the ACTUAL active Python executable, not a managed copy
@@ -2606,8 +2625,8 @@ def _first_time_setup(self, interactive=True) -> Dict:
26062625 defaults = self._get_sensible_defaults(managed_python_exe_str)
26072626 final_config = defaults.copy()
26082627
2609- # Interactive prompts (if not in CI )
2610- if interactive and (not os.environ.get("CI")) :
2628+ # Interactive prompts (ONLY if truly interactive )
2629+ if interactive:
26112630 safe_print(_("🌍 Welcome to omnipkg! Let's get you configured."))
26122631 safe_print("-" * 60)
26132632
@@ -2678,6 +2697,9 @@ def _first_time_setup(self, interactive=True) -> Dict:
26782697 input(_("Enable Python interpreter hotswapping? (y/n) [y]: ")).strip().lower()
26792698 )
26802699 final_config["enable_python_hotswap"] = hotswap_choice != "n"
2700+ else:
2701+ # Non-interactive: use all defaults
2702+ safe_print(_(" ✅ Using default configuration (non-interactive mode)"))
26812703
26822704 # Save configuration
26832705 try:
@@ -2694,7 +2716,7 @@ def _first_time_setup(self, interactive=True) -> Dict:
26942716 with open(self.config_path, "w") as f:
26952717 json.dump(full_config, f, indent=4)
26962718
2697- if interactive and (not os.environ.get("CI")) :
2719+ if interactive:
26982720 safe_print(_("\n✅ Configuration saved to {}.").format(self.config_path))
26992721 safe_print(_(" You can edit this file manually later."))
27002722 safe_print(_("🧠 Initializing omnipkg knowledge base..."))
@@ -2710,7 +2732,7 @@ def _first_time_setup(self, interactive=True) -> Dict:
27102732 "-y",
27112733 ]
27122734 try:
2713- if interactive and (not os.environ.get("CI")) :
2735+ if interactive:
27142736 process = subprocess.Popen(
27152737 rebuild_cmd,
27162738 stdout=subprocess.PIPE,
@@ -2735,7 +2757,7 @@ def _first_time_setup(self, interactive=True) -> Dict:
27352757 else:
27362758 subprocess.run(rebuild_cmd, check=True, capture_output=True, text=True)
27372759 except subprocess.CalledProcessError:
2738- if interactive and (not os.environ.get("CI")) :
2760+ if interactive:
27392761 safe_print(_(" ⚠️ Knowledge base will be built on first command usage instead."))
27402762 pass
27412763
0 commit comments