Design & grounding doc · 2026-06-28 · branch feat/codex-provider · sources verified against the live Codex CLI 0.142.3 on this machine and openai/codex main
/goal brief references it.
| Ask | Verdict | Mechanism (grounded) |
|---|---|---|
| Add Codex accounts | FEASIBLE | Drive codex login (PKCE browser OAuth, same UX as Anthropic flow) → force cli_auth_credentials_store=file → read ~/.codex/auth.json → decode identity (email/plan/account_id) from the id_token JWT claims. Importing an already-logged-in auth.json is the fallback path. |
| Query Codex usage | FEASIBLE | Official, documented path: spawn codex app-server (JSON-RPC), initialize, call account/rateLimits/read → primary=5h, secondary=weekly, each {usedPercent, windowDurationMins, resetsAt}. Maps 1:1 onto jacked's existing five_hour/seven_day cache. Not the ToS-risky wham/usage scrape; not the rollout-jsonl file (null since gpt-5.4, openai/codex#14880). |
| Claude/Codex UI indicator | FEASIBLE | Add a provider field to AccountResponse + the menubar summary; render a small provider mark on each account entry in the web dashboard, the pinned panel/dropdown, and (for the single active account) the menubar pill glyph. |
| Install skills/commands for Codex | FEASIBLE w/ caveats | Codex's extensibility converged ~1:1 with Claude Code. Skills → ~/.agents/skills/<name>/SKILL.md (same open agentskills.io format; live path confirmed populated on this machine). Commands → ~/.codex/prompts/*.md (and/or explicit-only skills). Behavioral rules → ~/.codex/AGENTS.md. Gatekeeper hooks → ~/.codex/hooks.json (same events incl. PermissionRequest) but each hook hits a trust-review gate. Agents are TOML, not MD (conversion). |
| Switch Codex accounts | FEASIBLE w/ guardrails | No native multi-account (openai/codex#4432 unmerged). Swap ~/.codex/auth.json in file mode or per-account CODEX_HOME isolation (preferred, matches jacked's CLAUDE_CONFIG_DIR pattern). Hard constraints: single-use rotating refresh tokens (#15502/#9634) → capture the live file before swapping out, never replay a stale snapshot; stock Codex caches auth at startup → restart required; preserve auth_mode+last_refresh on write. |
Every facet has a single clean chokepoint. The build extends each behind a provider dimension rather than forking parallel code.
| Subsystem | Single chokepoint (file:line) | What it does |
|---|---|---|
| Account model | jacked/web/database.py:38-86 (Pydantic) + :201-235 (DDL) | One SQLite accounts table at ~/.claude/jacked.db. UNIQUE(email, organization_uuid); organization_uuid uses "" sentinel (not NULL) so upserts work. No provider column exists. |
| Add account (OAuth) | jacked/web/oauth.py:39-56 (constants), :195-251 (PKCE flow) | PKCE OAuth against Anthropic, callback on localhost:45100-45199, has a purpose axis ('primary'/'claude_code') but no provider axis. Endpoints hardwired to claude.com / api.anthropic.com. |
| Usage fetch | jacked/web/auth.py:733 fetch_usage(); the provider-specific leaf is :810-832 | GET https://api.anthropic.com/api/oauth/usage with Bearer + anthropic-beta header → parse five_hour/seven_day {utilization, resets_at} → DB cache (database.py:1147). Pacing/coordinator/429-backoff are already provider-agnostic. |
| Credential write / switch | jacked/api/credential_helpers.py:604 sync_credential_to_all_stores() | The ONLY credential writer. Builds the {claudeAiOauth, _jackedAccountId} blob (build_oauth_data :540), writes 4 stores: ~/.claude/.credentials.json, macOS Keychain Claude Code-credentials, ~/.claude.json, per-account dir. Manual swap = routes/auth.py:1093; auto-swap = usage_monitor.py:702. |
| Per-account launch | jacked/launch.py (per-account dir via CLAUDE_CONFIG_DIR) | Seeds ~/.claude/accounts/<id>/ and runs Claude with CLAUDE_CONFIG_DIR so accounts run concurrently. Codex analog: CODEX_HOME. |
| Installer | jacked/cli.py:2973-3257 _run_install() + install_manifest.py:38-44 CATEGORIES | Copies jacked/data/{skills,commands,agents,lenses,templates} into ~/.claude/*, injects rules into ~/.claude/CLAUDE.md, registers hooks in ~/.claude/settings.json. Content-hash manifest at ~/.claude/jacked-manifest.json drives idempotency/prune. .claude path is hardcoded at every call site; only _jacked_home() is parameterized. Known bug: only SKILL.md is copied — skill sidecar files (e.g. aesthetic-dogfood-audit/measure.js) are dropped. |
| UI account tags | dashboard data/web/js/components/accounts.js:247-348; panel panel.js:89-129; pill service/menubar_mac.py:284-308 | Cards/rows render from GET /api/auth/accounts → AccountResponse (routes/auth.py:137-177, populated _account_to_response :381-424). Panel popover + pinned panel both load /panel. Pill shows only the active account (drawn "J" glyph, menubar_mac.py:139-143). |
~/.codex/auth.json{
"OPENAI_API_KEY": null, // set only in apikey mode
"auth_mode": "chatgpt", // apikey | chatgpt | chatgptAuthTokens | ...
"tokens": {
"id_token": "<JWT>", // identity claims live here
"access_token": "<JWT>",
"refresh_token": "<token>", // SINGLE-USE, rotates on refresh
"account_id": "<org/workspace id>" // the org sentinel analog
},
"last_refresh": "2026-..Z"
}
tokens.id_token (base64url middle segment): email, and under https://api.openai.com/auth: chatgpt_plan_type (plus/pro/team/business/…), chatgpt_user_id, chatgpt_account_id. Use account_id as the org sentinel (normalize to "" when absent, mirroring Claude).cli_auth_credentials_store = file | keyring | auto in config.toml. On this machine the key is unset (→ auto) but auth.json exists at mode 0600. jacked must force file (write the key) or it can silently miss keyring-stored creds (Keychain service Codex Auth).https://auth.openai.com, client_id app_EMoamEEZ73f0CkXaXp7hrann (env-overridable), callback localhost:1455. Refresh when last_refresh > ~8 days or on 401 → rotates the refresh_token.codex app-server JSON-RPC// 1. spawn: codex app-server (stdin/stdout JSON-RPC; ~500ms settle after initialize)
// 2. send: initialize → initialized
// 3. call: account/rateLimits/read
{ "rateLimits": {
"primary": { "usedPercent": 37.0, "windowDurationMins": 300, "resetsAt": 1782... }, // 5h
"secondary": { "usedPercent": 81.0, "windowDurationMins": 10080, "resetsAt": 1782... } // weekly
},
"rateLimitResetCredits": { "availableCount": N } // "banked resets" — Claude has no analog
}
// also: account/read → { authMode, planType } push: account/rateLimits/updated (subscribe instead of poll)
Normalize primary→five_hour, secondary→seven_day ({utilization, resets_at}) and feed the unchanged cache columns + menubar. API-key accounts: skip ChatGPT-plan polling (openai/codex#15272); the org Usage/Cost admin API is a separate, later path.
| jacked artifact | Codex target | Notes |
|---|---|---|
skill data/skills/<n>/SKILL.md | ~/.agents/skills/<n>/SKILL.md live, populated | Same open format. Also mirror to ~/.codex/skills and verify via /skills; copy sidecar files (fix the SKILL.md-only drop). |
command data/commands/*.md | ~/.codex/prompts/*.md (or explicit-only skill) | Prompts invoked /prompts:name; deprecated in favor of skills but functional. Dir absent today → create it. |
rules (jacked_behaviors.md) | append block to ~/.codex/AGENTS.md | Block-delimited like the CLAUDE.md injection; AGENTS.md exists (empty) on this machine. |
| gatekeeper hooks | ~/.codex/hooks.json (PreToolUse + PermissionRequest) | Same JSON stdin/stdout contract. Trust gate: each hook is hashed and must be approved in /hooks unless managed — surface this, don't silently fail. |
agents data/agents/*.md | ~/.codex/agents/<n>.toml | Format conversion MD→TOML (name/description/developer_instructions). Lower priority. |
| MCP | [mcp_servers.*] in config.toml / codex mcp add | Optional; later. |
CODEX_HOME=~/.codex/accounts/<id> isolation (own auth.json + config + sessions). Sidesteps the single-file refresh race entirely. Matches jacked's existing per-account-dir launch pattern.file storage, (2) copy the current live ~/.codex/auth.json back into the outgoing account's slot (capture rotated tokens), (3) copy incoming into place field-complete (keep auth_mode+last_refresh), (4) require/signal a Codex restart. Refuse to replay a stale snapshot. Single-writer-per-account.wham/usage scrape that some community switchers use. jacked runs on several machines: never sync one account's auth.json across machines (rotation invalidates, #15502).provider column (default 'claude'); rebuild UNIQUE(provider,email,organization_uuid) via the crash-safe table-rebuild pattern (database.py:586-644); thread through Pydantic Account, AccountResponse, create_account, the WS-safe whitelist; make active_account_id per-provider.~/.codex/auth.json (respect $CODEX_HOME), detect file-vs-keyring from config.toml, decode the id_token JWT, normalize account_id as org sentinel. Keyring mode reports "not swappable" rather than silently missing.POST /accounts/add?provider=codex + CLI: force file storage, drive codex login (or import the live auth.json), persist a provider='codex' row from decoded identity; idempotent re-add.fetch_usage leaf (auth.py:810-832) to dispatch on account.provider; Codex impl drives codex app-server → account/rateLimits/read → normalized five_hour/seven_day; Anthropic path unchanged (regression test green).provider in the JSON; render the Claude/Codex mark in dashboard cards (accounts.js:313), panel/popover rows (panel.js:98/113), and the pill glyph (menubar_mac.py:139-143); keep the 6-account-fit layout; polish per make-interfaces-feel-better.CODEX_HOME launch (jacked codex <id>) + in-place active-swap with write-back, field-complete write, restart signal, single-writer guard, stale-snapshot refusal.~/.codex); deploy skills→~/.agents/skills (+sidecars), commands→~/.codex/prompts, rules→~/.codex/AGENTS.md, gatekeeper→~/.codex/hooks.json; make the manifest target-aware for idempotent install + uninstall. Verify Codex discovers an installed skill/prompt.Next (post-run): Codex auto-swap engine driven by app-server usage tiers; agent MD→TOML export; MCP install into Codex; account/rateLimits/updated push instead of polling; per-model sub-limit + banked-reset affordances in the UI.
codex login (robust) rather than reimplementing OpenAI PKCE in-app — same browser-OAuth UX, less fragility from env-overridable endpoints.CODEX_HOME isolation; in-place swap is the parity affordance with guardrails.active_account_id becomes per-provider (a user is active on Claude AND Codex simultaneously).~/.agents/skills + ~/.codex/skills and verify.Codex auth/config/usage/skills/hooks verified against openai/codex main and developers.openai.com (mid-2026); switching behavior from community tools + issues.
token_data.rs, manager.rs; CI/CD: .../ci-cd-authdocs/plans/2026-03-24-gstack-integration-design.mdGenerated for the jacked × Codex build. Grounding for the matching /goal brief; every claim is anchored to a file:line or a cited URL. Verify against the installed Codex version before hardcoding paths — Codex is post-cutoff and evolving.