diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9eb4a92f..64637e18 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -163,7 +163,7 @@ jobs: python-version: "3.14" - name: Install planoai - run: pip install ./cli + run: pip install -e ./cli - name: Validate plano config run: bash config/validate_plano_config.sh diff --git a/Dockerfile b/Dockerfile index 43bb5d0c..1e27f8ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -72,7 +72,7 @@ COPY cli/README.md ./ COPY config/plano_config_schema.yaml /config/plano_config_schema.yaml COPY config/envoy.template.yaml /config/envoy.template.yaml -RUN uv run pip install --no-cache-dir . +RUN pip install --no-cache-dir -e . COPY cli/planoai planoai/ COPY config/envoy.template.yaml . diff --git a/cli/planoai/main.py b/cli/planoai/main.py index 82d2039f..4d72a12e 100644 --- a/cli/planoai/main.py +++ b/cli/planoai/main.py @@ -179,7 +179,7 @@ def build(docker): cwd=crates_dir, check=True, ) - console.print("[green]✓[/green] WASM plugins built") + log.info("WASM plugins built") except subprocess.CalledProcessError as e: console.print(f"[red]✗[/red] WASM build failed: {e}") sys.exit(1) @@ -197,7 +197,7 @@ def build(docker): cwd=crates_dir, check=True, ) - console.print("[green]✓[/green] brightstaff built") + log.info("brightstaff built") except subprocess.CalledProcessError as e: console.print(f"[red]✗[/red] brightstaff build failed: {e}") sys.exit(1) @@ -319,7 +319,7 @@ def up(file, path, foreground, with_tracing, tracing_port, docker): console.print(f" [dim]{validation_stderr.strip()}[/dim]") sys.exit(1) - console.print(f"[green]✓[/green] Configuration valid") + log.info("Configuration valid") # Set up environment default_otel = ( diff --git a/cli/planoai/native_binaries.py b/cli/planoai/native_binaries.py index 6a255bdf..cbfd5f0f 100644 --- a/cli/planoai/native_binaries.py +++ b/cli/planoai/native_binaries.py @@ -100,15 +100,13 @@ def ensure_envoy_binary(): with open(version_path, "r") as f: cached_version = f.read().strip() if cached_version == ENVOY_VERSION: - log.info(f"Envoy {ENVOY_VERSION} found at {envoy_path}") + log.info(f"Envoy {ENVOY_VERSION} (cached)") return envoy_path - print( + log.info( f"Envoy version changed ({cached_version} → {ENVOY_VERSION}), re-downloading..." ) else: - log.info( - f"Envoy binary found at {envoy_path} (unknown version, re-downloading...)" - ) + log.info("Envoy binary found (unknown version, re-downloading...)") slug = _get_platform_slug() url = ( @@ -123,6 +121,7 @@ def ensure_envoy_binary(): try: _download_file(url, tmp_path, label=f"Envoy {ENVOY_VERSION}") + log.info(f"Extracting Envoy {ENVOY_VERSION}...") with tarfile.open(tmp_path, "r:xz") as tar: # Find the envoy binary inside the archive envoy_member = None @@ -150,7 +149,6 @@ def ensure_envoy_binary(): os.chmod(envoy_path, 0o755) with open(version_path, "w") as f: f.write(ENVOY_VERSION) - log.info(f"Envoy {ENVOY_VERSION} installed at {envoy_path}") return envoy_path finally: @@ -187,7 +185,7 @@ def ensure_wasm_plugins(): # 1. Local source build (inside repo) local = _find_local_wasm_plugins() if local: - log.info(f"Using locally-built WASM plugins: {local[0]}") + log.info("Using locally-built WASM plugins") return local # 2. Cached download @@ -201,9 +199,9 @@ def ensure_wasm_plugins(): with open(version_path, "r") as f: cached_version = f.read().strip() if cached_version == version: - log.info(f"WASM plugins {version} found at {PLANO_PLUGINS_DIR}") + log.info(f"WASM plugins {version} (cached)") return prompt_gw_path, llm_gw_path - print( + log.info( f"WASM plugins version changed ({cached_version} → {version}), re-downloading..." ) else: @@ -220,6 +218,7 @@ def ensure_wasm_plugins(): url = f"{PLANO_RELEASE_BASE_URL}/{version}/{gz_name}" gz_dest = dest + ".gz" _download_file(url, gz_dest, label=f"{name} ({version})") + log.info(f"Decompressing {name}...") with gzip.open(gz_dest, "rb") as f_in, open(dest, "wb") as f_out: shutil.copyfileobj(f_in, f_out) os.unlink(gz_dest) @@ -235,7 +234,7 @@ def ensure_brightstaff_binary(): # 1. Local source build (inside repo) local = _find_local_brightstaff() if local: - log.info(f"Using locally-built brightstaff: {local}") + log.info("Using locally-built brightstaff") return local # 2. Cached download @@ -248,9 +247,9 @@ def ensure_brightstaff_binary(): with open(version_path, "r") as f: cached_version = f.read().strip() if cached_version == version: - log.info(f"brightstaff {version} found at {brightstaff_path}") + log.info(f"brightstaff {version} (cached)") return brightstaff_path - print( + log.info( f"brightstaff version changed ({cached_version} → {version}), re-downloading..." ) else: @@ -265,6 +264,7 @@ def ensure_brightstaff_binary(): gz_path = brightstaff_path + ".gz" _download_file(url, gz_path, label=f"brightstaff ({version}, {slug})") + log.info("Decompressing brightstaff...") with gzip.open(gz_path, "rb") as f_in, open(brightstaff_path, "wb") as f_out: shutil.copyfileobj(f_in, f_out) os.unlink(gz_path) @@ -272,7 +272,6 @@ def ensure_brightstaff_binary(): os.chmod(brightstaff_path, 0o755) with open(version_path, "w") as f: f.write(version) - log.info(f"brightstaff {version} installed at {brightstaff_path}") return brightstaff_path diff --git a/cli/planoai/native_runner.py b/cli/planoai/native_runner.py index 8331d698..c5ccd6e7 100644 --- a/cli/planoai/native_runner.py +++ b/cli/planoai/native_runner.py @@ -161,19 +161,10 @@ def start_native(plano_config_file, env, foreground=False, with_tracing=False): """Start Envoy and brightstaff natively.""" from planoai.core import _get_gateway_ports - console = None - try: - from rich.console import Console - - console = Console() - except ImportError: - pass - - def status_print(msg): - if console: - console.print(msg) - else: - print(msg) + # Stop any existing instance first + if os.path.exists(NATIVE_PID_FILE): + log.info("Stopping existing Plano instance...") + stop_native() envoy_path = ensure_envoy_binary() ensure_wasm_plugins() @@ -182,7 +173,7 @@ def start_native(plano_config_file, env, foreground=False, with_tracing=False): plano_config_file, env, with_tracing=with_tracing ) - status_print(f"[green]✓[/green] Configuration rendered") + log.info("Configuration rendered") log_dir = os.path.join(PLANO_RUN_DIR, "logs") os.makedirs(log_dir, exist_ok=True) @@ -233,7 +224,7 @@ def start_native(plano_config_file, env, foreground=False, with_tracing=False): # Health check gateway_ports = _get_gateway_ports(plano_config_file) - status_print(f"[dim]Waiting for listeners to become healthy...[/dim]") + log.info("Waiting for listeners to become healthy...") start_time = time.time() timeout = 60 @@ -244,35 +235,35 @@ def start_native(plano_config_file, env, foreground=False, with_tracing=False): all_healthy = False if all_healthy: - status_print(f"[green]✓[/green] Plano is running (native mode)") + log.info("Plano is running (native mode)") for port in gateway_ports: - status_print(f" [cyan]http://localhost:{port}[/cyan]") + log.info(f" http://localhost:{port}") break # Check if processes are still alive if not _is_pid_alive(brightstaff_pid): - status_print("[red]✗[/red] brightstaff exited unexpectedly") - status_print(f" Check logs: {os.path.join(log_dir, 'brightstaff.log')}") + log.error("brightstaff exited unexpectedly") + log.error(f" Check logs: {os.path.join(log_dir, 'brightstaff.log')}") _kill_pid(envoy_pid) sys.exit(1) if not _is_pid_alive(envoy_pid): - status_print("[red]✗[/red] envoy exited unexpectedly") - status_print(f" Check logs: {os.path.join(log_dir, 'envoy.log')}") + log.error("envoy exited unexpectedly") + log.error(f" Check logs: {os.path.join(log_dir, 'envoy.log')}") _kill_pid(brightstaff_pid) sys.exit(1) if time.time() - start_time > timeout: - status_print(f"[red]✗[/red] Health check timed out after {timeout}s") - status_print(f" Check logs in: {log_dir}") + log.error(f"Health check timed out after {timeout}s") + log.error(f" Check logs in: {log_dir}") stop_native() sys.exit(1) time.sleep(1) if foreground: - status_print(f"[dim]Running in foreground. Press Ctrl+C to stop.[/dim]") - status_print(f"[dim]Logs: {log_dir}[/dim]") + log.info("Running in foreground. Press Ctrl+C to stop.") + log.info(f"Logs: {log_dir}") try: import glob @@ -290,13 +281,13 @@ def start_native(plano_config_file, env, foreground=False, with_tracing=False): ) tail_proc.wait() except KeyboardInterrupt: - status_print(f"\n[dim]Stopping Plano...[/dim]") + log.info("Stopping Plano...") if tail_proc.poll() is None: tail_proc.terminate() stop_native() else: - status_print(f"[dim]Logs: {log_dir}[/dim]") - status_print(f"[dim]Run 'planoai down' to stop.[/dim]") + log.info(f"Logs: {log_dir}") + log.info("Run 'planoai down' to stop.") def _daemon_exec(args, env, log_path): @@ -364,7 +355,7 @@ def _kill_pid(pid): def stop_native(): """Stop natively-running Envoy and brightstaff processes.""" if not os.path.exists(NATIVE_PID_FILE): - print("No native Plano instance found (PID file missing).") + log.info("No native Plano instance found (PID file missing).") return with open(NATIVE_PID_FILE, "r") as f: @@ -383,7 +374,7 @@ def stop_native(): log.info(f"{name} (PID {pid}) already stopped") continue except PermissionError: - log.info(f"Permission denied stopping {name} (PID {pid})") + log.error(f"Permission denied stopping {name} (PID {pid})") continue # Wait for graceful shutdown @@ -403,7 +394,7 @@ def stop_native(): pass os.unlink(NATIVE_PID_FILE) - print("Plano stopped (native mode).") + log.info("Plano stopped (native mode).") def native_validate_config(plano_config_file):