import gzip import os import platform import shutil import sys import tarfile import tempfile import planoai from planoai.consts import ( ENVOY_VERSION, PLANO_BIN_DIR, PLANO_PLUGINS_DIR, PLANO_RELEASE_BASE_URL, ) from planoai.utils import find_repo_root, getLogger log = getLogger(__name__) def _get_platform_slug(): """Return the platform slug for binary downloads.""" system = platform.system().lower() machine = platform.machine().lower() mapping = { ("linux", "x86_64"): "linux-amd64", ("linux", "aarch64"): "linux-arm64", ("darwin", "arm64"): "darwin-arm64", } slug = mapping.get((system, machine)) if slug is None: if system == "darwin" and machine == "x86_64": print( "Error: macOS x86_64 (Intel) is not supported. " "Pre-built binaries are only available for Apple Silicon (arm64)." ) sys.exit(1) print( f"Error: Unsupported platform {system}/{machine}. " "Supported platforms: linux-amd64, linux-arm64, darwin-arm64" ) sys.exit(1) return slug def _download_file(url, dest, label=None): """Download a file from *url* to *dest* with a progress bar.""" import urllib.request import urllib.error if label is None: label = os.path.basename(dest) try: response = urllib.request.urlopen(url) total = int(response.headers.get("Content-Length", 0)) downloaded = 0 block_size = 64 * 1024 with open(dest, "wb") as f: while True: chunk = response.read(block_size) if not chunk: break f.write(chunk) downloaded += len(chunk) if total > 0: pct = downloaded * 100 // total bar_len = 30 filled = bar_len * downloaded // total bar = "█" * filled + "░" * (bar_len - filled) mb = downloaded / (1024 * 1024) total_mb = total / (1024 * 1024) print( f"\r {label} {bar} {pct}% ({mb:.1f}/{total_mb:.1f} MB)", end="", flush=True, ) print() # newline after progress bar except urllib.error.URLError as e: print(f"\nError downloading {label}: {e}") print(f" URL: {url}") print("Please check your internet connection and try again.") sys.exit(1) def ensure_envoy_binary(): """Download Envoy binary if not already present or version changed. Returns path to binary.""" envoy_path = os.path.join(PLANO_BIN_DIR, "envoy") version_path = os.path.join(PLANO_BIN_DIR, "envoy.version") if os.path.exists(envoy_path) and os.access(envoy_path, os.X_OK): # Check if cached binary matches the pinned version if os.path.exists(version_path): with open(version_path, "r") as f: cached_version = f.read().strip() if cached_version == ENVOY_VERSION: log.info(f"Envoy {ENVOY_VERSION} (cached)") return envoy_path log.info( f"Envoy version changed ({cached_version} → {ENVOY_VERSION}), re-downloading..." ) else: log.info("Envoy binary found (unknown version, re-downloading...)") slug = _get_platform_slug() url = ( f"https://github.com/tetratelabs/archive-envoy/releases/download/" f"{ENVOY_VERSION}/envoy-{ENVOY_VERSION}-{slug}.tar.xz" ) os.makedirs(PLANO_BIN_DIR, exist_ok=True) with tempfile.NamedTemporaryFile(suffix=".tar.xz", delete=False) as tmp: tmp_path = tmp.name 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 for member in tar.getmembers(): if member.name.endswith("/bin/envoy") or member.name == "bin/envoy": envoy_member = member break if envoy_member is None: print("Error: Could not find envoy binary in the downloaded archive.") print("Archive contents:") for member in tar.getmembers(): print(f" {member.name}") sys.exit(1) # Extract just the binary f = tar.extractfile(envoy_member) if f is None: print("Error: Could not extract envoy binary from archive.") sys.exit(1) with open(envoy_path, "wb") as out: out.write(f.read()) os.chmod(envoy_path, 0o755) with open(version_path, "w") as f: f.write(ENVOY_VERSION) return envoy_path finally: if os.path.exists(tmp_path): os.unlink(tmp_path) def _find_local_wasm_plugins(): """Check for WASM plugins built from source. Returns (prompt_gw, llm_gw) or None.""" repo_root = find_repo_root() if not repo_root: return None wasm_dir = os.path.join(repo_root, "crates", "target", "wasm32-wasip1", "release") prompt_gw = os.path.join(wasm_dir, "prompt_gateway.wasm") llm_gw = os.path.join(wasm_dir, "llm_gateway.wasm") if os.path.exists(prompt_gw) and os.path.exists(llm_gw): return prompt_gw, llm_gw return None def _find_local_brightstaff(): """Check for brightstaff binary built from source. Returns path or None.""" repo_root = find_repo_root() if not repo_root: return None path = os.path.join(repo_root, "crates", "target", "release", "brightstaff") if os.path.exists(path) and os.access(path, os.X_OK): return path return None def ensure_wasm_plugins(): """Find or download WASM plugins. Checks: local build → cached download → fresh download.""" # 1. Local source build (inside repo) local = _find_local_wasm_plugins() if local: log.info("Using locally-built WASM plugins") return local # 2. Cached download version = planoai.__version__ version_path = os.path.join(PLANO_PLUGINS_DIR, "wasm.version") prompt_gw_path = os.path.join(PLANO_PLUGINS_DIR, "prompt_gateway.wasm") llm_gw_path = os.path.join(PLANO_PLUGINS_DIR, "llm_gateway.wasm") if os.path.exists(prompt_gw_path) and os.path.exists(llm_gw_path): if os.path.exists(version_path): with open(version_path, "r") as f: cached_version = f.read().strip() if cached_version == version: log.info(f"WASM plugins {version} (cached)") return prompt_gw_path, llm_gw_path log.info( f"WASM plugins version changed ({cached_version} → {version}), re-downloading..." ) else: log.info("WASM plugins found (unknown version, re-downloading...)") # 3. Download from GitHub releases (gzipped) os.makedirs(PLANO_PLUGINS_DIR, exist_ok=True) for name, dest in [ ("prompt_gateway.wasm", prompt_gw_path), ("llm_gateway.wasm", llm_gw_path), ]: gz_name = f"{name}.gz" 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) with open(version_path, "w") as f: f.write(version) return prompt_gw_path, llm_gw_path def ensure_brightstaff_binary(): """Find or download brightstaff binary. Checks: local build → cached download → fresh download.""" # 1. Local source build (inside repo) local = _find_local_brightstaff() if local: log.info("Using locally-built brightstaff") return local # 2. Cached download version = planoai.__version__ brightstaff_path = os.path.join(PLANO_BIN_DIR, "brightstaff") version_path = os.path.join(PLANO_BIN_DIR, "brightstaff.version") if os.path.exists(brightstaff_path) and os.access(brightstaff_path, os.X_OK): if os.path.exists(version_path): with open(version_path, "r") as f: cached_version = f.read().strip() if cached_version == version: log.info(f"brightstaff {version} (cached)") return brightstaff_path log.info( f"brightstaff version changed ({cached_version} → {version}), re-downloading..." ) else: log.info("brightstaff found (unknown version, re-downloading...)") # 3. Download from GitHub releases (gzipped) slug = _get_platform_slug() filename = f"brightstaff-{slug}.gz" url = f"{PLANO_RELEASE_BASE_URL}/{version}/{filename}" os.makedirs(PLANO_BIN_DIR, exist_ok=True) 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) os.chmod(brightstaff_path, 0o755) with open(version_path, "w") as f: f.write(version) return brightstaff_path def find_wasm_plugins(): """Find WASM plugin files built from source. Returns (prompt_gateway_path, llm_gateway_path).""" repo_root = find_repo_root() if not repo_root: print( "Error: Could not find repository root. " "Make sure you're inside the plano repository." ) sys.exit(1) wasm_dir = os.path.join(repo_root, "crates", "target", "wasm32-wasip1", "release") prompt_gw = os.path.join(wasm_dir, "prompt_gateway.wasm") llm_gw = os.path.join(wasm_dir, "llm_gateway.wasm") missing = [] if not os.path.exists(prompt_gw): missing.append("prompt_gateway.wasm") if not os.path.exists(llm_gw): missing.append("llm_gateway.wasm") if missing: print(f"Error: WASM plugins not found: {', '.join(missing)}") print(f" Expected at: {wasm_dir}/") print(" Run 'planoai build' first to build them.") sys.exit(1) return prompt_gw, llm_gw def find_brightstaff_binary(): """Find the brightstaff binary built from source. Returns path.""" repo_root = find_repo_root() if not repo_root: print( "Error: Could not find repository root. " "Make sure you're inside the plano repository." ) sys.exit(1) brightstaff_path = os.path.join( repo_root, "crates", "target", "release", "brightstaff" ) if not os.path.exists(brightstaff_path): print(f"Error: brightstaff binary not found at {brightstaff_path}") print(" Run 'planoai build' first to build it.") sys.exit(1) return brightstaff_path