Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: CI

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Set persist-credentials: false on the checkout step.

actions/checkout@v4 persists the GitHub token in .git/config by default. Subsequent shell-script steps in this workflow could inadvertently read it. Add persist-credentials: false to prevent credential leakage.

🛡️ Proposed fix
       - uses: actions/checkout@v4
+        with:
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 15-15: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml at line 15, Update the actions/checkout@v4 step to
set persist-credentials: false, preventing the GitHub token from being stored in
the repository’s Git configuration.

Source: Linters/SAST tools


- name: Install system test dependencies
run: sudo apt-get update && sudo apt-get install -y fzf jq

- name: Install uv
run: python -m pip install --disable-pip-version-check uv==0.9.5

- name: Check shell syntax
run: bash -n bin/codex bin/codex-auth install.sh lib/codex-auth/*.sh tests/run.sh

- name: Run shell tests
run: bash tests/run.sh

- name: Sync locked TUI environment
run: uv sync --project tui --locked

- name: Run TUI tests
run: uv run --project tui pytest -q
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
.DS_Store
*.log
*.tmp
*.pyc
.pytest_cache/
.venv/
__pycache__/
tmp/
/reports/
/assets/.frames/
/node_modules/
/auth.json
/auth-profiles/
/auth-backups/
/auth-state.json
/active-profile.json
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Changelog

## 0.1.0 - 2026-07-10

- Added the persistent Textual watch and autoswitch UI.
- Added safe capture of the current Codex login as a named profile.
- Added per-profile earned reset counts and confirmed reset redemption.
- Added generation-bound in-session auth reload support for patched Codex.
- Added automatic patch rebuilds after official curl-installed Codex updates.
- Added token-rotation lineage checks, atomic profile writes, and idempotent reset retries.
- Fixed auto mode treating an invalid or slow unrelated profile as a failed full-pool refresh.
- Added deterministic synthetic screenshots, GIF/video demos, and Linux CI.
34 changes: 34 additions & 0 deletions PRODUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Product

## Register

product

## Users

Codex CLI users who keep several saved ChatGPT auth profiles and need to see quota, choose an account, or leave automatic rotation running from a terminal. The primary user is already in an active coding workflow and needs the auth tool to stay understandable without becoming another process tree to babysit.

## Product Purpose

Codex Rolling Auth keeps the live Codex auth file on a usable saved profile. Success means one persistent screen shows current quota and freshness, manual switching is deliberate, and live automatic switching can run with an inspectable reason and no stale-cache guesswork.

## Brand Personality

Quiet, direct, trustworthy. The interaction reference is `claude-swap`: low chrome, dense readable usage rows, literal state labels, and keyboard behavior that disappears into the task.

## Anti-references

Do not recreate the one-shot `fzf` selector as a full-screen app. Avoid decorative terminal effects, nested panels, ambiguous color-only status, surprise credential changes, generic dashboard copy, and controls whose live or dry-run state is unclear.

## Design Principles

1. Show the actual state first: active profile, quota, automatic reset time, earned reset count, data age, and reload readiness.
2. Make safety visible: passive watch, dry-run auto, and live auto must look and behave differently.
3. Keep one source of truth: the TUI renders structured snapshots and typed engine decisions rather than duplicating policy.
4. Explain every switch: show the chosen profile, the comparison, and the reason in plain language.
5. Stay terminal-native: keyboard-first, compact, stable at narrow widths, and useful without animation.
6. Treat earned resets as scarce: refresh before offering one, default confirmation to Cancel, never combine redemption with a hidden profile switch, and preserve idempotency across retries.

## Accessibility & Inclusion

Every action is available from the keyboard. Selection, active state, errors, stale data, and live mode use text or symbols in addition to color. Focus is explicit only when selection is armed. Layout must remain readable in narrow terminals, and state changes must not depend on decorative motion.
148 changes: 138 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,60 @@
# Codex Rolling Auth

Small shell wrapper for switching Codex to the best saved ChatGPT auth profile before a session starts.
Small shell wrapper for switching Codex to the best saved ChatGPT auth profile before or during a session.

It keeps `auth.json` pointed at the best available profile before a session starts. The optional `codex` shim only does a cached auth selection and then execs the real Codex binary, so normal Codex launches do not create nested runners, live log monitors, or extra MCP/app-server sidecars.

![Codex rolling auth selector](assets/usage-selector.png)
[![CI](https://github.com/editnori/codex-rolling-auth/actions/workflows/ci.yml/badge.svg)](https://github.com/editnori/codex-rolling-auth/actions/workflows/ci.yml)

![Codex rolling auth selector scroll](assets/usage-selector.gif)
![Codex Auth watch screen with synthetic work and backup profiles](assets/codex-auth-watch.png)

_Synthetic demo data. No live credentials or account identifiers._

<details>
<summary>Watch the keyboard flow</summary>

![Codex Auth TUI demo showing profile switching and earned reset confirmation](assets/codex-auth-demo.gif)

</details>

| Dry-run autoswitch | Earned reset confirmation |
| --- | --- |
| ![Dry-run autoswitch screen](assets/codex-auth-auto.png) | ![Earned reset confirmation](assets/codex-auth-reset.png) |

All captures use the real Textual app with an in-memory synthetic backend.

## Requirements

- Linux or WSL with Bash, `jq`, `flock`, Git, and the official Codex CLI.
- [`uv`](https://docs.astral.sh/uv/) for the isolated Textual environment.
- `crontab` is optional; without it, run `codex-auth maintain` after a direct curl update.
- Building the patched Codex generation also needs a Rust/Cargo toolchain and normal native build dependencies.

## Install

If Codex came from the official curl installer, install or update Codex first. That installer owns the visible `~/.local/bin/codex` link. `./install.sh --wrap-codex` installs a one-minute maintenance job that restores the rolling-auth shim after a later curl update and queues the matching patch build.

```bash
curl -fsSL https://chatgpt.com/codex/install.sh | sh
```

```bash
git clone https://github.com/editnori/codex-rolling-auth.git
cd codex-rolling-auth
./install.sh --wrap-codex
```

The wrapper resolves the native binary through `$CODEX_HOME/packages/standalone/current/bin/codex` (and the legacy `current/codex` layout), so it follows the curl installer's current release instead of falling back to an older Bun/npm copy.

That installs:

- `codex-auth`, the profile manager and rolling runner
- `codex-auth-tui`, the full-screen watcher in a private project `.venv`
- `codex`, an optional shim that runs `codex-auth auto --quiet --no-background` and then starts the real Codex binary
- patched-Codex selection: when a matching patched binary exists, the shim uses it with in-process rolling auth enabled; when Codex updates, the shim falls back to stock Codex
- patched-Codex selection: when a matching generation exists, the shim uses it with in-process rolling auth enabled; when Codex updates, the shim immediately runs stock Codex and starts one detached patch build
- a marked, idempotent cron entry that runs `codex-auth maintain --quiet` once per minute without replacing existing cron jobs

The installer uses `uv` with the platform's native TLS store to create the TUI environment under `PREFIX/lib/codex-auth/tui/.venv`. It does not install Python packages globally. Packaging and isolated tests can copy the project without resolving dependencies by setting `CODEX_AUTH_TUI_SKIP_BOOTSTRAP=1`; normal installs should leave bootstrap enabled.

If you only want the manager and not the `codex` shim:

Expand All @@ -30,18 +64,79 @@ If you only want the manager and not the `codex` shim:

## Usage

Open the persistent account monitor without changing auth:

```bash
codex-auth watch
```

Run the autoswitch policy in dry-run mode. It refreshes and shows decisions but does not switch accounts:

```bash
codex-auth watch --auto
```

Allow the watcher to apply decisions through a generation-bound compare-and-switch transaction:

```bash
codex-auth watch --auto --live
```

`codex-auth tui` is an alias for `codex-auth watch`. `--threshold 0` means proactively prefer any strictly better ready account. In that mode cooldown is the anti-flap guard; hysteresis applies when the threshold is above zero.

Each auto tick trusts only usage returned by that refresh generation. A profile that needs a new sign-in is marked unavailable instead of keeping an old bar, and an unrelated unavailable profile does not block fresh profiles from being evaluated. If the active session itself is invalidated, live auto can recover to a fresh ready profile through the same generation-bound switch guard.

Watcher keys:

- `s` arms or disarms manual selection.
- `n` saves the current Codex auth as a named profile.
- `u` checks and uses an earned rate-limit reset for a selected profile.
- Arrow keys or `j`/`k` move between accounts.
- `Enter` confirms an armed switch and keeps the watcher open.
- `Esc` disarms first, then goes back or quits.
- `a` opens the autoswitch view.
- `l` toggles LIVE; entering live asks for confirmation, while returning to dry-run is immediate.
- `r` forces a refresh.
- `q` goes back or quits.

Live mode changes `auth.json`, so new Codex processes use the selected account immediately. A stock Codex process that is already running does not hot-reload that file. In-session switching requires a patched Codex build that matches the installed native binary:

```bash
codex-auth patch-codex
codex-auth patch-codex --status
```

The automatic builder requires the exact `rust-v<installed-version>` source tag. It never stamps `origin/main` as a release match. The patch reloads a deliberate account change before Codex can refresh the old account's token, including the unauthorized-recovery path that normally reports that you logged out or signed in to another account. If the installed Codex release changes or a build fails, the shim stays on stock Codex until that exact generation is ready.

Save the current login as a profile:

```bash
codex-auth add Layth08 --current
codex-auth add work --current
```

From `codex-auth tui`, press `n`, enter the profile name, and press `Enter`.
Replacing an existing profile requires confirmation.

### Earned resets

When Codex reports an earned reset bank, each profile card shows its authoritative remaining count separately from the automatic `5h` and weekly reset countdowns. Press `u`, select a ChatGPT profile, and confirm. The TUI refreshes that profile before confirmation, uses one reset through Codex app-server, then refreshes the usage bars and remaining count. It does **not** switch the active profile.

The equivalent explicit CLI command requires confirmation:

```bash
codex-auth reset work --yes
```

Ambiguous transport retries reuse the same idempotency key, so retrying cannot spend a second reset. `alreadyRedeemed` is treated as success; `nothingToReset` and `noCredit` do not pretend a reset happened. See the official [Codex app-server earned reset contract](https://learn.chatgpt.com/docs/app-server#8-earned-rate-limit-resets-chatgpt).

Open the usage selector:

```bash
codex-auth usage --refresh --select
```

![Inline usage selector with synthetic profiles](assets/usage-selector.png)

Run a rolling Codex session explicitly:

```bash
Expand Down Expand Up @@ -78,6 +173,12 @@ Build the patched Codex binary in the foreground:
codex-auth patch-codex
```

Reconcile the curl-installed command and queue a missing generation manually:

```bash
codex-auth maintain
```

Terminate only the direct MCP sidecars under legacy `--yolo` Codex processes:

```bash
Expand All @@ -88,24 +189,45 @@ codex-auth doctor --kill-sidecars --yes

- Profiles live under `$CODEX_HOME/auth-profiles` by default.
- The live auth file stays at `$CODEX_HOME/auth.json`.
- `bin/codex-auth` is a thin entrypoint. Runtime code lives in `lib/codex-auth/*.sh` by area: core, profiles, usage, render, selector, patch, run, and doctor.
- Saved profiles contain live credentials. Files are written with restrictive permissions, but they must never be committed, uploaded, pasted into issues, or included in captures.
- `$CODEX_HOME/active-profile.json` records the selected profile using hashed account identity and a credential fingerprint. When Codex rotates that account's refresh token, the wrapper compare-and-swaps the newer auth back into the same saved profile; a real account change or concurrent edit is never overwritten.
- Usage probes run in temporary Codex homes. If a probe rotates a refresh token, the rotated auth is persisted before the temporary home is removed and is attached to usage state only after the profile lineage check succeeds.
- `bin/codex-auth` is a thin entrypoint. Shell runtime code lives in `lib/codex-auth/*.sh`; the persistent watcher lives in the isolated project under `lib/codex-auth/tui` after installation.
- Set `CODEX_AUTH_CODEX_BIN=/path/to/codex` if the wrapper cannot find your real Codex binary.
- Set `CODEX_AUTH_AUTO=0` to bypass automatic profile selection for one command.
- `codex-auth run` is explicit opt-in. It can monitor a bounded session log to retry after usage-limit errors, but the normal `codex` shim does not use it.
- Set `CODEX_AUTH_ROLL_WATCH=1` to enable in-session periodic profile checks for `codex-auth run`; it is off by default to avoid background sidecar churn.
- Set `CODEX_AUTH_ROLL_LIVE_MONITOR=0` to disable live log monitoring for `codex-auth run`.
- Set `CODEX_AUTH_PATCH_AUTO=0` to stop the `codex` shim from using or building patched Codex.
- Set `CODEX_AUTH_PATCH_BUILD_AUTO=1` if you want the `codex` shim to start a background rebuild when the patched binary is stale.
- Patched Codex builds live under `$CODEX_HOME/patched-codex`. The shim only uses a patched binary when its marker matches the current stock Codex version/hash.
- Background rebuilds are on by default. Set `CODEX_AUTH_PATCH_BUILD_AUTO=0` to use an existing matching patch without building a missing one.
- Patched binaries live under `$CODEX_HOME/patched-codex/generations/<stock-key>/codex`. Each marker and binary are published together as one immutable directory, and the shim never selects a stale generation.
- The builder hashes a stock binary once in the detached worker, reuses a shared Cargo target, strips release debug data, and retains two patched/source generations by default. Set `CODEX_AUTH_PATCH_KEEP_GENERATIONS` to change retention.
- Build failures back off for 15 minutes by default while stock Codex remains usable. Set `CODEX_AUTH_PATCH_RETRY_SECS` to change the retry window.
- The maintenance cron waits for the official standalone installer lock, restores the shim only when the curl installer owns the visible command, and queues the new generation. Set `CODEX_AUTH_INSTALL_MAINTAIN_CRON=0` during installation to opt out.
- Patched builds are stamped as the installed Codex version plus `+local`, so they do not appear older to Codex's update check.
- Set `CODEX_AUTH_REFRESH_JOBS` to tune concurrent usage refreshes. The default is 4 and `CODEX_AUTH_REFRESH_JOBS_MAX` caps it at 4 unless you explicitly change the cap.
- Set `CODEX_AUTH_REFRESH_JOBS` to tune concurrent usage refreshes. The default is 4 and `CODEX_AUTH_REFRESH_JOBS_MAX` caps it at 4 unless you explicitly change the cap; `usage --sync` temporarily raises the default up to the profile count, capped at 12.
- `codex-auth doctor --kill-sidecars --yes` does not kill the Codex TUI processes; it only terminates direct MCP sidecars under legacy `--yolo` Codex roots.
- Set `CODEX_AUTH_USAGE_HEADER=1` or `CODEX_AUTH_USAGE_STATUS=1` if you want the full table header or status column back.
- The selector defaults to the inline fzf scrolling TUI when fzf is available. It uses adaptive height instead of taking over the whole terminal. Set `CODEX_AUTH_NO_FZF=1` or `CODEX_AUTH_SELECTOR=numbered` for the plain numbered fallback.
- `codex-auth usage --refresh --select` opens the selector from cached usage and refreshes in the background. Set `CODEX_AUTH_SELECT_SYNC_REFRESH=1` or pass `--sync` if you want to wait for a full refresh before the selector opens.
- `codex-auth usage --sync` refreshes usage first, then opens the TUI. `codex-auth usage --refresh --select` opens the TUI from cache right away and refreshes in the background.
- Set `CODEX_AUTH_SELECTOR_CENTER=1` if you want the fallback selector vertically centered in the terminal.
- Selector bars default to background-color lanes with thin horizontal row borders. Set `CODEX_AUTH_SELECTOR_BAR_STYLE=glyph` if your terminal does not render those cleanly.

## Remove the wrapper

Remove the marked maintenance block first so it cannot restore the shim, reinstall stock Codex, then remove the manager files:

```bash
crontab -l 2>/dev/null \
| awk '$0 == "# BEGIN codex-auth maintain" {skip=1; next} $0 == "# END codex-auth maintain" {skip=0; next} !skip' \
| crontab -
curl -fsSL https://chatgpt.com/codex/install.sh | sh
rm -f ~/.local/bin/codex-auth ~/.local/bin/codex-auth-tui ~/.local/bin/codex-real
rm -rf ~/.local/lib/codex-auth
```

This intentionally leaves `$CODEX_HOME/auth-profiles` and other account state in place.

## Test

```bash
Expand All @@ -114,6 +236,12 @@ tests/run.sh

## Regenerate Assets

Media generation is development-only and uses synthetic in-memory profiles. It requires Fontconfig, FFmpeg, ImageMagick, and the locked Playwright/Pillow dependencies:

```bash
uv sync --project tui --dev --locked
uv run --project tui playwright install chromium
uv run --project tui python scripts/capture_tui.py
uv run --project tui python scripts/capture_tui.py --check
node scripts/generate-assets.mjs
```
Binary file added assets/codex-auth-auto.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/codex-auth-demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/codex-auth-demo.mp4
Binary file not shown.
Binary file added assets/codex-auth-reset.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/codex-auth-watch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/usage-selector.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified assets/usage-selector.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading