Busy Codex

Busy Codex

by @wowlocal

Live Codex task status, account limits and weekly progress, with a reasoning effort dial and START-button Fast mode control for Codex Desktop and a native-control CLI fork.

zero-install aimonitorinfoanimation

How to run

This app is a single Python file that talks directly to the BUSY Bar HTTP API: no libraries, no setup. Connect your bar via USB and run:

python app.py

Over USB the bar is always at 10.0.4.20. On Wi-Fi, pass the bar's IP with --host <address>.

No bar at hand? Run the BUSY Bar Emulator and point the app at it: python app.py --host 127.0.0.1:8080.

To keep it running without a terminal open, install it with busybar-manager, which handles the virtualenv, autostart and scheduling, and lets you switch between apps from a web dashboard.

Source code

app.py
#!/usr/bin/env python3
"""Busy Codex: task status, account quotas and tactile reasoning effort.

    python3 app.py                         # USB BUSY Bar, local Codex
    python3 app.py --host 192.168.1.50      # Wi-Fi; BUSYBAR_TOKEN if required
    python3 app.py --host 127.0.0.1:8080 --demo  # emulator, no Codex or secrets

Download the complete app folder, not this entrypoint alone. Python 3.9+;
standard library only. macOS foreground targeting follows Codex Desktop or
the active CLI terminal. CLI controls require the native-control fork:
https://github.com/wowlocal/codex/tree/codex/native-tui-control
Fast needs a build with fast/set; restart CLI sessions after updating.
Stock CLI remains usable for account limits, but cannot accept control changes.

Reads local Codex session metadata and invokes the installed `codex app-server`
only to read account limits. It does not send prompts, edit Codex config,
install background services, update executables or restart exited processes.
SIGINT/SIGTERM stops the owned processes and clears only this app's canvas.
"""
from __future__ import annotations

import argparse
import json
import math
import os
from pathlib import Path
import signal
import socket
import subprocess
import sys
import threading
import time
import urllib.error
import urllib.parse
import urllib.request

APP = "busy-codex"
HERE = Path(__file__).resolve().parent
STOP = threading.Event()


def arguments(argv=None):
    parser = argparse.ArgumentParser(description=__doc__,
                                     formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument("--host", default="10.0.4.20", metavar="IP[:PORT]")
    parser.add_argument("--port", type=int, default=18765,
                        help="local report listener, separate from installed busy-codex (18765)")
    parser.add_argument("--demo", action="store_true",
                        help="cycle real effort animations without opening Codex data")
    parser.add_argument("--test", action="store_true",
                        help="show a synthetic effort frame and exit; no Codex needed")
    parser.add_argument("--no-effort", action="store_true", help="read-only status and limits")
    parser.add_argument("--no-upload", action="store_true",
                        help="reuse this version's assets already uploaded to the Bar")
    parser.add_argument("--seconds", type=float, default=0,
                        help="stop automatically after this many seconds (0: until stopped)")
    args = parser.parse_args(argv)
    if not 1 <= args.port <= 65535:
        parser.error("--port must be between 1 and 65535")
    if args.seconds < 0 or not math.isfinite(args.seconds):
        parser.error("--seconds must be a finite, nonnegative number")
    host = args.host.removeprefix("http://").rstrip("/")
    parsed = urllib.parse.urlsplit("http://" + host)
    try:
        valid = bool(parsed.hostname) and parsed.port != 0
    except ValueError:
        valid = False
    if not valid or parsed.path or parsed.query or parsed.fragment or parsed.username:
        parser.error("--host must be an IP or hostname with an optional port")
    args.host = host
    return args


class Bar:
    """Small preview transport; normal mode uses the daemon's USB-aware transport."""

    def __init__(self, host):
        from busybar_http import local_opener

        self.base = "http://" + host + "/api"
        source = os.environ.get("BUSYBAR_USB_SOURCE_IP", "10.0.4.21") if host == "10.0.4.20" else None
        self.opener = local_opener(source)
        self.headers = {}
        token = os.environ.get("BUSYBAR_TOKEN", "")
        if token:
            self.headers["X-API-Token"] = token

    def request(self, method, path, body=None, content_type="application/json"):
        headers = dict(self.headers)
        if body is not None:
            headers["Content-Type"] = content_type
        req = urllib.request.Request(self.base + path, data=body, method=method, headers=headers)
        try:
            with self.opener.open(req, timeout=2) as response:
                response.read()
                return response.status
        except urllib.error.HTTPError as exc:
            if exc.code in (401, 403):
                raise RuntimeError("BUSY Bar refused authentication; check BUSYBAR_TOKEN") from exc
            return exc.code

    def clear(self):
        try:
            self.request("DELETE", "/display/draw?application_name=" + APP)
        except OSError:
            pass


def demo(args):
    """Emit the same BGRA animation frames used to generate native .anim assets.

    PNG transport makes those frames reproducible with the gallery recorder,
    whose animation decoder does not support our native inter-frame encoding.
    The clock selects a current frame after every send; delayed frames are
    skipped rather than collected in a queue.
    """
    import effort_animation
    import fast_animation
    from pixel_ui import encode_png

    bar = Bar(args.host)
    levels = ("high", "ultra", "fast", "normal")
    started = time.monotonic()
    deadline = started + (args.seconds or float("inf"))
    retry_delay = .25
    index = 0
    print(f"{APP} demo -> {bar.base}; synthetic data only", flush=True)
    try:
        while not STOP.is_set() and time.monotonic() < deadline:
            elapsed = time.monotonic() - started
            level = "ultra" if args.test else levels[int(elapsed / effort_animation.DURATION_S) % len(levels)]
            phase = elapsed % effort_animation.DURATION_S
            frame_index = 18 if args.test else int(phase * effort_animation.FPS)
            pixels = (fast_animation.frame(level == "fast", frame_index, entering=False)
                      if level in ("fast", "normal") else effort_animation.frame(level, frame_index, entering=False))
            name = f"demo-{index % 4}.png"
            index += 1
            try:
                status = bar.request("POST", f"/assets/upload?application_name={APP}&file={name}",
                                     encode_png(pixels, 72, 16), "application/octet-stream")
                if status == 200:
                    payload = {"application_name": APP, "priority": 30,
                               "elements": [{"id": "effort", "type": "image", "path": name,
                                             "x": 0, "y": 0, "timeout": 5}]}
                    status = bar.request("POST", "/display/draw", json.dumps(payload).encode())
                if status == 409:
                    STOP.wait(.5)
                    continue
                if status == 508:  # firmware still holds one of the rotating assets
                    STOP.wait(.1)
                    continue
                if status != 200:
                    raise RuntimeError(f"BUSY Bar returned HTTP {status}")
                retry_delay = .25
                if args.test:
                    STOP.wait(.25)
                    break
            except OSError as exc:
                if args.test:
                    raise RuntimeError(f"BUSY Bar unreachable: {type(exc).__name__}") from exc
                print(f"BUSY Bar unreachable: {type(exc).__name__}; retrying", file=sys.stderr)
                STOP.wait(retry_delay)
                retry_delay = min(5., retry_delay * 2)
                continue
            STOP.wait(1 / 12)
    finally:
        bar.clear()
    return 0


def runtime_env(args):
    env = dict(os.environ)
    # This focused launcher owns its report listener and renderer. Settings for
    # an independently installed multi-provider daemon must not leak into it.
    for key in ("BUSYBAR_HUB", "BUSYBAR_STANDBY", "BUSYBAR_HUB_TOKEN",
                "BUSYBAR_CODEX_THREAD_ID"):
        env.pop(key, None)
    env.update({"BUSYBAR_APP_NAME": APP, "BUSYBAR_DRAW_PRIORITY": "30", "BUSYBAR_MANAGED": "1",
                "BUSYBAR_PORT": str(args.port), "BUSYBAR_LISTEN": "127.0.0.1",
                "BUSYBAR_RENDER_MODE": "auto", "BUSYBAR_STYLE": "minimal",
                "BUSYBAR_AI_STATUS": "0", "BUSYBAR_X_PULSE": "0",
                "BUSYBAR_CODEX_EFFORT": "0" if args.no_effort else "1",
                "BUSYBAR_ASTRA_STATE": str(HERE / "disabled-astra-state.json"),
                "BUSYBAR_TRANSPORT": "usb" if args.host == "10.0.4.20" else "wifi",
                "BUSYBAR_DEVICE": args.host, "PYTHONUNBUFFERED": "1"})
    return env


def upload_assets(transport):
    assets = HERE / "assets"
    if not assets.is_dir():
        raise RuntimeError("Packaged assets are missing. Run scripts/build_gallery.py, then its app.py.")
    files = sorted(assets.glob("*.anim"))
    if not files:
        raise RuntimeError("Packaged animation assets are empty")
    print(f"Uploading {len(files)} animations into {APP}'s assets (no app installation)", flush=True)
    transport.clear(APP)
    for path in files:
        if STOP.is_set():
            return
        req = urllib.request.Request(
            transport.base + f"/assets/upload?application_name={APP}&file=" + urllib.parse.quote(path.name),
            data=path.read_bytes(), method="POST",
            headers={**transport.headers, "Content-Type": "application/octet-stream"})
        with transport.opener.open(req, timeout=8) as response:
            response.read()


def stop_children(children):
    for child in children:
        if child.poll() is None:
            child.terminate()
    deadline = time.monotonic() + 3.5
    for child in children:
        try:
            child.wait(timeout=max(.05, deadline - time.monotonic()))
        except subprocess.TimeoutExpired:
            child.kill()
            child.wait(timeout=1)


def wait_ready(child, port):
    opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
    deadline = time.monotonic() + 4
    while not STOP.is_set() and time.monotonic() < deadline:
        if child.poll() is not None:
            raise RuntimeError("Display worker exited before becoming ready")
        try:
            with opener.open(f"http://127.0.0.1:{port}/health", timeout=.2) as response:
                response.read()
                if response.status == 200:
                    return
        except OSError:
            pass
        STOP.wait(.05)
    if not STOP.is_set():
        raise RuntimeError("Display worker did not become ready")


def run(args):
    # Fail before drawing or starting anything when a prior instance owns the
    # local endpoint. It would be unsafe to feed an unrelated daemon's port.
    with socket.socket() as probe:
        try:
            probe.bind(("127.0.0.1", args.port))
        except OSError as exc:
            raise RuntimeError(f"Local port {args.port} is in use; choose --port or stop that instance") from exc
    env = runtime_env(args)
    os.environ.update(env)
    for key in ("BUSYBAR_HUB", "BUSYBAR_STANDBY", "BUSYBAR_HUB_TOKEN", "BUSYBAR_CODEX_THREAD_ID"):
        if key not in env:
            os.environ.pop(key, None)
    import daemon
    transport = daemon.make_transport()
    if not args.no_upload:
        upload_assets(transport)
    children = []
    deadline = time.monotonic() + (args.seconds or float("inf"))
    try:
        if STOP.is_set():
            return 0
        children.append(subprocess.Popen(
            [sys.executable, str(HERE / "daemon.py"), "--port", str(args.port)], cwd=HERE, env=env))
        wait_ready(children[0], args.port)
        if STOP.is_set():
            return 0
        children.append(subprocess.Popen(
            [sys.executable, str(HERE / "adapters" / "codex_status.py")], cwd=HERE, env=env))
        print(f"{APP} -> {args.host}; Ctrl-C stops both workers", flush=True)
        while not STOP.wait(.1) and time.monotonic() < deadline:
            if any(child.poll() is not None for child in children):
                raise RuntimeError("A worker exited; stopping the app (no automatic restart)")
    finally:
        stop_children(children)
        transport.clear(APP)
    return 0


def main(argv=None):
    args = arguments(argv)
    STOP.clear()
    signal.signal(signal.SIGINT, lambda *_: STOP.set())
    signal.signal(signal.SIGTERM, lambda *_: STOP.set())
    try:
        return demo(args) if args.demo or args.test else run(args)
    except (OSError, RuntimeError) as exc:
        print(f"{APP}: {exc}", file=sys.stderr)
        return 1


if __name__ == "__main__":
    sys.exit(main())
View project on GitHub →