A macOS CLI to switch between and run Codex CLI accounts in parallel. Logic lives in
crates/core; crates/cli is a thin shell. apps/tray is a SwiftUI menu-bar app (work in
progress) that reuses core through crates/ffi's uniffi bindings — same invariants, same
source of truth, no logic duplicated into Swift.
Each account keeps one real auth.json in ~/.codex-buddy/<alias>/. ~/.codex/auth.json is a
symlink to the active account's file. Switching only repoints that symlink; auth files are never
copied. This sidesteps OAuth refresh-token rotation: a credential exists as exactly one file,
refreshed in place, so there is never a stale copy that would force a re-login.
- Switched (per account;
~/.codex/<entry>symlinks to the active account's copy):auth.json,sessions/,history.jsonl. - Isolated (per account, never shared or linked): all sqlite (
sqlite/dir,*.sqlite*). - Shared (symlinked back to
~/.codex): everything else — config.toml, AGENTS.md, rules, ... - Parallel = run codex with
CODEX_HOME=<account dir>; switch = repoint the switched symlinks.
Hard requirement: codex's cli_auth_credentials_store must be file (keyring / auto / ephemeral
move or delete auth.json and break the scheme). config_check enforces this.
crates/core— all logic, no CLI or interactive IO. Modules:paths,error,auth,registry,layout,config_check,init,ops,doctor,usage,recommend,transfer,remote,running. Unit tests live insrc/<module>/tests.rs.crates/cli— arg parsing (pico-args), prompts, output; delegates everything to core. New command families live undersrc/commands/, while reusable JSON/output DTOs live insrc/output.rs; keepmain.rsfocused on dispatch and shared presentation.crates/ffi— uniffi bindings overcorefor the Swift tray (list_accounts,switchAccount,addAccount, ...). Thin: no business logic, just type conversion.coreandclistay free of any FFI dependency.crates/ffi-bindgen— a separateuniffi-bindgenbinary crate that generates the Swift bindings from the builtcodex-buddy-ffilibrary. Split out so itsuniffi/clifeature (pulls inclap) never ends up in the shipped cdylib's dependency graph.apps/tray— the SwiftUI menu-bar app (Swift Package, not part of the Cargo workspace). Runapps/tray/Scripts/build-ffi.shfirst to build the xcframework + generate the Swift bindings (both gitignored, regenerated fromcrates/ffi), thenswift buildinsideapps/tray.
- Code, comments, and strings are English. Comments stay minimal: a short
///on structs, fields, and important methods; only "why" notes in bodies; no module-level//!(except the one crate doc inlib.rs); no comments inCargo.toml/rust-toolchain.toml. Test functions get no doc unless there is something notable. - Dependencies stay tiny:
serde,serde_json,base64,pico-args. No tokio / async / HTTP / crypto / chrono. JWTs are decoded, never verified. The release profile is size-optimized. The one exception isuniffiincrates/ffi/crates/ffi-bindgen— required to bridge to Swift;coreandcliare still zero-FFI and unaffected. - Hard network rule: codex-buddy never talks to any backend itself — no HTTP code anywhere, and
the stored tokens are never used to call an API directly. The only remote-data path is the
remotemodule driving the localcodex app-serverover stdio, letting codex make its own request with its own auth and refresh. Remote fetching stays strictly opt-in (--remote, the tray's live-usage toggle); every default path is fully local. One deliberate exception: the tray's user-triggered "Check for Updates" GETs GitHub's release metadata — unauthenticated, nothing sent. The token/backend part of the rule has no exceptions. - Writes are atomic: the registry via temp-file + rename under a file lock; symlink repointing via
temp symlink + rename.
initis the only operation that touches existing~/.codexdata, and it is reversible (timestamped backup + rollback on any failure).
cargo test
cargo clippy --all-targets
cargo fmt --all
cargo build --release
Keep test / clippy / fmt all green before finishing a change.