#!/usr/bin/env python3 """ Codex Patcher Update Pipeline — check, update, patch, validate, test. Usage: python3 update_codex_patcher.py --check # Check for new version python3 update_codex_patcher.py --update # Download + install new binary python3 update_codex_patcher.py --validate # Validate 6 config targets python3 update_codex_patcher.py --patch # Apply config patches python3 update_codex_patcher.py --test # Integration test python3 update_codex_patcher.py --auto # Full cycle """ import json import os import sys import subprocess import argparse from pathlib import Path SCRIPT_DIR = Path(__file__).resolve().parent sys.path.insert(0, str(SCRIPT_DIR)) # ANSI colors GREEN = "\033[92m" YELLOW = "\033[93m" RED = "\033[91m" CYAN = "\033[96m" BOLD = "\033[1m" RESET = "\033[0m" def color(text, c): return f"{c}{text}{RESET}" def load_config(): """Load codex_config.json.""" config_path = SCRIPT_DIR / "codex_config.json" if not config_path.is_file(): print(f" {color('Config not found: ' + str(config_path), RED)}") sys.exit(1) with open(config_path) as f: return json.load(f) def cmd_check(config): """Check GitHub releases for new Codex version.""" print(f"\n{BOLD}Checking for updates...{RESET}") try: import urllib.request url = "https://api.github.com/repos/openai/codex/releases/latest" req = urllib.request.Request(url, headers={"User-Agent": "codex-patcher"}) with urllib.request.urlopen(req, timeout=15) as resp: data = json.loads(resp.read()) latest_tag = data.get("tag_name", "") # Tag format: "rust-v0.111.0" latest_version = latest_tag.replace("rust-v", "").replace("v", "") # Get installed version from codex_patcher import detect_codex _, installed = detect_codex() print(f" Installed: {CYAN}{installed}{RESET}") print(f" Latest: {CYAN}{latest_version}{RESET}") if installed == latest_version: print(f" {GREEN}Already up to date!{RESET}") return True else: print(f" {YELLOW}Update available: {installed} → {latest_version}{RESET}") return False except Exception as e: print(f" {color(f'Error: {e}', RED)}") return False def cmd_update(config): """Download and install new Codex binary via update-codex.sh.""" print(f"\n{BOLD}Updating Codex binary...{RESET}") update_script = SCRIPT_DIR / "update-codex.sh" if not update_script.is_file(): print(f" {color(f'update-codex.sh not found at {update_script}', RED)}") return False try: result = subprocess.run( ["bash", str(update_script)], timeout=300 ) return result.returncode == 0 except Exception as e: print(f" {color(f'Error: {e}', RED)}") return False def cmd_validate(config): """Validate all 6 config targets.""" print(f"\n{BOLD}Validating config targets...{RESET}") try: from updater.config_validator import validate_all, print_validation_report codex_dir = os.path.expanduser("~/.codex") results = validate_all(codex_dir, config) counts = print_validation_report(results) # Save report report_dir = SCRIPT_DIR / "reports" report_dir.mkdir(exist_ok=True) report_path = report_dir / "validation_report.json" summary = { "targets": [ {"name": t.name, "status": s, "message": m} for t, s, m in results ], "counts": counts, } with open(report_path, "w") as f: json.dump(summary, f, indent=2) print(f"\n Report saved: {report_path}") return counts.get("RED", 0) == 0 except Exception as e: print(f" {color(f'Error: {e}', RED)}") return False def cmd_patch(config): """Apply config patches.""" print(f"\n{BOLD}Applying patches...{RESET}") try: from codex_patcher import apply_all_patches ok, results = apply_all_patches(config) return ok except Exception as e: print(f" {color(f'Error: {e}', RED)}") return False def cmd_test(config): """Run integration test.""" print(f"\n{BOLD}Running integration test...{RESET}") base_url = config["base_url"].rstrip("/") if not base_url.endswith("/v1"): base_url += "/v1" env = os.environ.copy() env["OPENAI_BASE_URL"] = base_url env["OPENAI_API_KEY"] = config["api_key"] try: result = subprocess.run( ["codex", "exec", "--sandbox", "danger-full-access", "Reply with just the number 42"], capture_output=True, text=True, timeout=60, env=env ) output = result.stdout.strip() print(f" Output: {output[:200]}") if "42" in output: print(f" {GREEN}Test passed!{RESET}") return True else: print(f" {YELLOW}Unexpected output (no '42' found){RESET}") if result.stderr: print(f" Stderr: {result.stderr[:200]}") return False except subprocess.TimeoutExpired: print(f" {RED}Test timed out (60s){RESET}") return False except Exception as e: print(f" {color(f'Error: {e}', RED)}") return False def cmd_auto(config): """Full cycle: check → update → patch → validate → test.""" print(f"\n{BOLD}{'=' * 50}{RESET}") print(f"{BOLD} Codex Patcher — Auto Update Pipeline{RESET}") print(f"{BOLD}{'=' * 50}{RESET}") steps = [ ("Check version", cmd_check), ("Update binary", cmd_update), ("Apply patches", cmd_patch), ("Validate", cmd_validate), ("Test", cmd_test), ] for name, func in steps: ok = func(config) if not ok and name not in ("Check version",): print(f"\n {RED}Pipeline stopped at: {name}{RESET}") return False print(f"\n{GREEN}{'=' * 50}{RESET}") print(f"{GREEN} Pipeline completed successfully!{RESET}") print(f"{GREEN}{'=' * 50}{RESET}") return True def main(): parser = argparse.ArgumentParser( description="Codex Patcher Update Pipeline" ) parser.add_argument("--check", action="store_true", help="Check for new version") parser.add_argument("--update", action="store_true", help="Update binary") parser.add_argument("--validate", action="store_true", help="Validate config") parser.add_argument("--patch", action="store_true", help="Apply patches") parser.add_argument("--test", action="store_true", help="Run integration test") parser.add_argument("--auto", action="store_true", help="Full auto cycle") args = parser.parse_args() config = load_config() if args.auto: return 0 if cmd_auto(config) else 1 if args.check: return 0 if cmd_check(config) else 1 if args.update: return 0 if cmd_update(config) else 1 if args.validate: return 0 if cmd_validate(config) else 1 if args.patch: return 0 if cmd_patch(config) else 1 if args.test: return 0 if cmd_test(config) else 1 parser.print_help() return 0 if __name__ == "__main__": raise SystemExit(main())