clui 0.3.0

TUI for Claude Code and Codex usage limits
# /// script
# requires-python = ">=3.10"
# dependencies = []
# ///
"""PTY harness for clui's background auto-adaptation.

Plays a terminal that answers OSC 10/11 color queries and DA1, starting with
a light background and flipping to dark mid-run. Asserts that clui renders
the light default palette first and switches to the dark one after the flip.
"""
import fcntl
import os
import pty
import re
import select
import struct
import sys
import termios
import time

LIGHT = {"fg": b"rgb:1a1a/1c1c/2323", "bg": b"rgb:f4f4/f5f5/fafa"}
DARK = {"fg": b"rgb:f4f4/f5f5/fafa", "bg": b"rgb:0606/0707/0c0c"}

# Light palette marker: truecolor cyan-700 (14;116;144). Dark palette marker:
# the ring's indexed 237. Both appear in every frame that draws a gauge,
# and the footer accent guarantees colored output even while "Loading…".
LIGHT_SGR = b"38;2;14;116;144"
DARK_SGR = re.compile(rb"38;5;237|\x1b\[36m|;36m")

pid, master = pty.fork()
if pid == 0:
    os.environ["TERM"] = "xterm-256color"
    os.environ.pop("COLORFGBG", None)
    os.execv(sys.argv[1], [sys.argv[1]])

fcntl.ioctl(master, termios.TIOCSWINSZ, struct.pack("HHHH", 30, 100, 0, 0))

colors = LIGHT
flip_at = time.monotonic() + 3.0
deadline = time.monotonic() + 9.0
pending = b""
phases = {"light": b"", "dark": b""}
flipped = False

def respond(chunk: bytes) -> bytes:
    """Answer any complete OSC 10/11 query or DA1 in chunk; return leftover."""
    out = b""
    while True:
        m = re.search(rb"\x1b\]1([01]);\?(?:\x07|\x1b\\)|\x1b\[0?c", chunk)
        if not m:
            break
        if m.group(0).endswith(b"c"):
            out += b"\x1b[?62;22c"
        else:
            key = "fg" if m.group(1) == b"0" else "bg"
            out += b"\x1b]1" + m.group(1) + b";" + colors[key] + b"\x1b\\"
        chunk = chunk[: m.start()] + chunk[m.end() :]
    if out:
        os.write(master, out)
    # Keep only a small tail in case a query is split across reads.
    return chunk[-32:]

while time.monotonic() < deadline:
    if not flipped and time.monotonic() >= flip_at:
        colors = DARK
        flipped = True
    r, _, _ = select.select([master], [], [], 0.1)
    if master in r:
        try:
            data = os.read(master, 65536)
        except OSError:
            break
        if not data:
            break
        phases["dark" if flipped else "light"] += data
        pending = respond(pending + data)

os.write(master, b"q")
time.sleep(0.5)
try:
    os.kill(pid, 15)
except ProcessLookupError:
    pass

# Ignore the first 2.5s after the flip: clui re-checks every 2s, so frames
# drawn before the next check legitimately still use the light palette.
ok = True
if LIGHT_SGR not in phases["light"]:
    print("FAIL: light palette never rendered before the flip")
    ok = False
if DARK_SGR.search(phases["light"]):
    print("FAIL: dark palette rendered while background was light")
    ok = False
if not DARK_SGR.search(phases["dark"]):
    print("FAIL: dark palette never rendered after the flip")
    ok = False
if LIGHT_SGR in phases["dark"].split(b"\x1b[2J")[-1]:
    print("FAIL: last frame after flip still uses the light palette")
    ok = False
print("PASS" if ok else "FAIL")
sys.exit(0 if ok else 1)