#!/usr/bin/env python3 """Launch an interactive Codex TUI in tmux and approve its requests. This is deliberately an interactive Codex session, not ``codex exec``. A small daemon watches only tmux sessions created with the configured prefix and presses the first, one-shot approval choice when a known Codex approval overlay has been stable for multiple polls. Typical use:: python3 codex_auto_run.py -p "implement the task and run the tests" python3 codex_auto_run.py -p "fix it" -- --model gpt-5.4 python3 codex_auto_run.py --resume-last -C ~/code python3 codex_auto_run.py --resume SESSION_ID -p "continue the task" python3 codex_auto_run.py --resume -C ~/code python3 codex_auto_run.py --status python3 codex_auto_run.py --stop-daemon The wrapper owns ``-p``/``--prompt``. Codex itself uses ``-p`` for profiles; use ``--codex-profile NAME`` or place Codex options after ``--``. Security model -------------- By default the watcher approves Codex command, edit, permission, and network approval overlays. It does not answer ordinary questions, enable full access, trust hooks, install plugins, or approve MCP/app tool calls. Use ``--approve-mcp`` and ``--auto-trust-directory`` only when those broader side effects are intended. ``--bypass`` maps to Codex's dangerous no-sandbox mode and should only be used inside an externally isolated container or VM. """ from __future__ import annotations import argparse import fcntl import hashlib import json import os import re import secrets import shlex import shutil import signal import subprocess import sys import time from dataclasses import dataclass from datetime import datetime from pathlib import Path from typing import TextIO SCRIPT_VERSION = 1 DEFAULT_SESSION_PREFIX = "codex-auto" DEFAULT_POLL_INTERVAL = 0.35 DEFAULT_COOLDOWN = 0.8 DEFAULT_STABILITY_POLLS = 2 DEFAULT_REARM_INTERVAL = 2.0 DEFAULT_IDLE_EXIT_SECONDS = 120.0 MAX_LOG_BYTES = 2 * 1024 * 1024 LOG_BACKUPS = 3 MAX_OVERLAY_ROWS = 80 ANSI_ESCAPE_RE = re.compile(r"\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1b\\))") SELECTED_FIRST_RE = re.compile(r"^[›>]\s*1\.\s+(?P