codexctl

command module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 27, 2026 License: MIT Imports: 4 Imported by: 0

README

codexctl

codexctl is a small, local-only profile switcher for Codex accounts.

codexctl login personal
codexctl login work --device-auth
codexctl import existing        # save the login Codex already has
codexctl use personal
codexctl list -v
codexctl current
codexctl show work
codexctl sync
codexctl rename personal home
codexctl remove work
codexctl logout existing
codexctl restart-daemon         # make a running Codex daemon reload the switch
codexctl uninstall --purge      # remove codexctl and every saved profile

list, current, show, and doctor accept --json for scripting.

Interactive terminal UI

On a terminal, run codexctl on its own to open a dashboard of your profiles. Move with the arrow keys, press Enter to switch, and use single keys to rename (r), remove (d), log out (l), log in (n), import (i), restart the daemon (R), run doctor (D) or update (U).

Every command also works without its arguments on a terminal:

  • use, show, rename and logout let you pick the profile from a list you can filter by typing.
  • remove shows a checkbox list, so several profiles can go at once, and asks before deleting them.
  • login, import and rename ask for the new name and check it as you type. login also asks how to sign in; an API key or access token is typed into a masked field rather than your shell history.
  • Yes/no questions, such as restarting the daemon or uninstalling, use buttons, and anything that removes data is marked in red.
  • Slow work (update, doctor, logout, daemon restarts) runs behind a spinner or a progress bar, and list, show, current and doctor print styled tables, cards and checklists.

Output that is piped or redirected, --json, and runs without a terminal keep the plain text and exact behavior scripts rely on. Set CODEXCTL_NO_TUI=1 (or TERM=dumb) to turn the UI off everywhere; NO_COLOR turns off colors only. Cancelling a prompt with Esc or Ctrl-C exits with status 130.

It keeps Codex's normal configuration, history, sessions, skills, and plugins in the same CODEX_HOME. Only the file-backed login cache is switched. This makes the selected account visible to the Codex CLI and other Codex clients that use the same CODEX_HOME.

Install

Install script

On Linux, macOS, FreeBSD, and WSL:

curl -fsSL https://raw.githubusercontent.com/AbdelrhmanSaid/codexctl/master/install.sh | sh

On Windows, from PowerShell:

irm https://raw.githubusercontent.com/AbdelrhmanSaid/codexctl/master/install.ps1 | iex

Both scripts download the release archive for your platform, check its SHA-256 against the release's checksums.txt, and install the codexctl executable. install.sh also verifies the Ed25519 signature on checksums.txt when the local openssl supports it. On Unix systems the executable goes to /usr/local/bin when that is writable, otherwise to ~/.local/bin. On Windows it goes to %LOCALAPPDATA%\Programs\codexctl, which is added to your user PATH.

Options pick a release or an install directory:

curl -fsSL https://raw.githubusercontent.com/AbdelrhmanSaid/codexctl/master/install.sh | sh -s -- --version 0.2.1 --dir ~/bin
& ([scriptblock]::Create((irm https://raw.githubusercontent.com/AbdelrhmanSaid/codexctl/master/install.ps1))) -Version 0.2.1 -InstallDir C:\Tools\codexctl

The same settings can be given as the CODEXCTL_VERSION and CODEXCTL_INSTALL_DIR environment variables. Run the script with --help (or read the header of install.ps1) for the full list.

With Go
go install github.com/AbdelrhmanSaid/codexctl@latest

This requires Go 1.24 or newer. Make sure Go's binary directory (usually $HOME/go/bin) is on PATH.

Prebuilt binaries

Download the archive for your operating system and CPU from the latest GitHub release, extract it, and move codexctl (or codexctl.exe on Windows) to a directory on PATH. Releases are provided for macOS, Linux, Windows, and FreeBSD on x86-64 and ARM64. SHA-256 hashes are published in checksums.txt with every release.

Linux releases also include native packages:

  • Debian and Ubuntu: download the matching .deb, then run sudo apt install ./codexctl_*.deb.
  • Fedora, RHEL, and related distributions: download the matching .rpm, then run sudo rpm -i codexctl_*.rpm.
  • Alpine Linux: download the matching .apk, then run sudo apk add --allow-untrusted ./codexctl_*.apk.
  • Arch Linux: download the matching .pkg.tar.zst, then run sudo pacman -U ./codexctl_*.pkg.tar.zst.
Build locally
go build -o codexctl .

Shell completion is available through codexctl completion bash, zsh, fish, or powershell.

Updating
codexctl update --check   # report whether a newer release exists
codexctl update           # download, verify, and replace this executable
codexctl update --to 0.2.1

update downloads the release archive for the current platform from GitHub, checks its SHA-256 against the release's checksums.txt, and verifies the Ed25519 signature on that file with a public key built into the binary. A release that fails either check is never installed. The new executable is written next to the old one and renamed over it; on Windows the old file is moved aside as codexctl.exe.old and cleaned up by the next update.

Binaries installed with a Linux package or go install are told to update the same way they were installed. --force overrides that, and is also required to downgrade with --to. update never contacts the network unless you run it.

Uninstalling
codexctl uninstall                        # remove the executable, keep saved profiles
codexctl uninstall --purge                # also delete ~/.codexctl and every saved profile
codexctl uninstall --purge --keep-binary  # delete only the saved profiles and state

uninstall lists what it will remove and asks before doing it; run without flags on a terminal, it lets you check what to remove. Pass --yes to skip the question, which is required when stdin is not a terminal. It never touches CODEX_HOME: the active auth.json and the cli_auth_credentials_store = "file" setting in config.toml stay in place, so Codex stays logged in with the currently selected account. Deleted profiles cannot be recovered, so accounts that are not active must log in again.

Binaries installed with a Linux package are not removed; uninstall those with the package manager, after codexctl uninstall --purge --keep-binary if you also want the profiles gone. On Windows, install.ps1 added the install directory to your user PATH; remove that entry by hand if you no longer need it.

Releasing

Pushing a semantic-version tag creates a GitHub release with native archives, Linux packages, and checksums:

git tag -a v0.2.1 -m "codexctl v0.2.1"
git push origin v0.2.1

Releases are signed so that codexctl update can verify them. The Ed25519 private key lives in the CODEXCTL_SIGNING_KEY repository secret and the matching public key is embedded in internal/update/sign.go. To create a key:

go run ./tools/sign keygen -out signing.key
gh secret set CODEXCTL_SIGNING_KEY < signing.key

Then paste the printed public key into internal/update/sign.go. Binaries only trust the key they were built with, so rotate a key by publishing one release, signed with the old key, that embeds the new one.

To validate the release locally without publishing it, install GoReleaser and run:

goreleaser check
CODEXCTL_SIGNING_KEY=$(cat signing.key) goreleaser release --snapshot --clean
go run ./tools/sign verify dist/checksums.txt

Snapshot builds in CI skip signing.

How it works

Profiles are stored as private files:

~/.codexctl/
├── current
├── lock
├── switched
└── profiles/
    ├── personal.json
    └── work.json

lock serializes codexctl operations. It is an OS-level file lock, so it is released automatically if codexctl crashes or is interrupted; the file itself stays in place and never needs to be removed.

codexctl login NAME runs the official codex login command with an isolated temporary CODEX_HOME. A successful login is validated, saved, and activated. A cancelled or failed login leaves the active account untouched.

codexctl use NAME atomically copies that profile to $CODEX_HOME/auth.json (normally ~/.codex/auth.json). Before switching away, it saves any token refreshes Codex wrote for the current profile. If the account ID unexpectedly changed, it refuses to overwrite the saved profile.

A switch also records a short-lived recovery marker before updating auth.json and current. If the process is interrupted between those writes, the next successful login or use completes the interrupted switch before continuing.

codexctl import NAME saves the auth.json Codex is already using as a profile and selects it, for accounts that were logged in with plain codex login. It refuses an account that is already saved under another name.

codexctl sync saves token refreshes from the active auth.json into the selected profile on demand. login and use do this automatically before switching away.

codexctl show NAME and codexctl list -v print the account ID, email, plan, and last refresh time read from the saved snapshot. Tokens and API keys are never printed.

codexctl rename OLD NEW renames a saved profile. If it is the selected profile, any token refreshes are saved first and the selection follows the new name. codexctl remove NAME... deletes saved profiles. Removing the selected profile clears the selection but leaves auth.json in place, so Codex stays logged in until you use another profile.

codexctl logout NAME ends the account's session: it runs the official codex logout in an isolated temporary CODEX_HOME that holds only that profile's credentials, then deletes the profile. If the profile was selected and the active auth.json holds the same account, that file is removed too, since its tokens are no longer usable.

Because swapping auth.json only works with file-backed credentials, login and use ensure this root setting exists in $CODEX_HOME/config.toml:

cli_auth_credentials_store = "file"

Profile directories use mode 0700 and credential/state files use 0600 on POSIX systems. Credential contents are never printed. Symlinked credential and state paths are refused.

The Codex app-server daemon

Codex v0.157.0 and later runs a shared app-server daemon in the background and connects the CLI, IDE extensions, and the desktop app to it. The daemon reads auth.json once, when it starts, and keeps those credentials in memory. It does not watch the file, and its protocol has no request that reloads it, so after codexctl use a running daemon keeps using the previous account until it is restarted. Plain codex login has the same gap.

codexctl handles this as follows:

  • After every command that changes auth.json (login, use, logout of the selected profile, or any command that finishes an interrupted switch), codexctl checks whether a daemon is running for the same CODEX_HOME. If one is, it says so and, when run from a terminal, asks whether to restart it now. The default answer is no. In scripts and pipes it never asks and never restarts; it prints a reminder instead.
  • codexctl restart-daemon restarts the daemon on demand. It runs codex app-server daemon restart with the same CODEX_HOME. On a terminal it asks first; --yes skips the question.
  • A restart interrupts every Codex session that runs on the daemon. Codex tries to resume interrupted threads after a restart, but a turn that was in progress may be lost, so let running work finish first.
  • codexctl never restarts a daemon silently and never starts one. Since codex app-server daemon restart would start a daemon when none is running, codexctl restarts only when the daemon's pid file names a live process and its control socket accepts a connection. If those disagree, it reports why and does nothing.
Detecting and resolving a mismatch

codexctl current and codexctl doctor warn when the running daemon started before codexctl last changed auth.json:

$ codexctl doctor
ok   file-backed credential storage is configured
ok   active auth.json is valid JSON and is not a symlink
ok   2 saved profile(s)
ok   active auth.json matches profile work
warn Codex app-server daemon (pid 12345) started before codexctl switched to profile "work" and still uses the previous credentials; run 'codexctl restart-daemon' to reload it (this interrupts active Codex sessions)

codexctl current --json reports the same condition as "daemon_stale": true.

The check compares the daemon's start time with the time recorded in ~/.codexctl/switched, which codexctl writes whenever it changes auth.json. That file holds the CODEX_HOME, the profile name, and a timestamp, never credentials. The daemon's start time is read from its pid file on macOS and taken from that file's modification time elsewhere. codexctl does not ask the daemon which account it holds: that would need a WebSocket client for the app-server protocol, and the daemon's account query contacts the network.

To resolve a mismatch, run codexctl restart-daemon once no Codex session is doing work you would miss. Until then the daemon keeps using the previous account, and if it refreshes that account's tokens it writes them to auth.json over the newly selected profile. codexctl's account-ID check refuses to save such a file into the selected profile, so saved profiles stay intact, and running codexctl use NAME again restores the right credentials.

Important behavior

  • Restart already-running Codex CLI, IDE, or app processes after switching; they may retain credentials in memory. The shared app-server daemon always does; see above.
  • Do not use codex logout to switch profiles. Logout is broader than a local file swap and can invalidate a session you intended to keep. Use codexctl logout NAME when you do want to end a specific account's session.
  • Two simultaneously running clients that use the same CODEX_HOME still share one active account. Separate CODEX_HOME directories are required for truly parallel accounts.
  • An administrator-enforced keyring credential policy can override user config; codexctl doctor catches the common local configuration problems, but cannot override managed policy.
  • auth.json contains live credentials. Do not commit, sync, or share ~/.codexctl.

License

MIT

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
internal
cli
codex
Package codex runs the official Codex CLI as a child process.
Package codex runs the official Codex CLI as a child process.
daemon
Package daemon inspects the shared Codex app-server daemon that Codex v0.157.0 and later starts by default.
Package daemon inspects the shared Codex app-server daemon that Codex v0.157.0 and later starts by default.
tui
Package tui draws codexctl's interactive prompts and styled output with Bubble Tea and Lip Gloss.
Package tui draws codexctl's interactive prompts and styled output with Bubble Tea and Lip Gloss.
update
Package update fetches signed codexctl releases from GitHub and replaces the running executable.
Package update fetches signed codexctl releases from GitHub and replaces the running executable.
tools
sign command
Command sign manages the Ed25519 key that signs codexctl releases.
Command sign manages the Ed25519 key that signs codexctl releases.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL