jacked × Codex — Dual-Provider Integration

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. Teach jacked — today a Claude-only account monitor/switcher/installer — to treat OpenAI Codex as a first-class second provider: add Codex accounts, read Codex usage (5h + weekly windows) in the same UI with a Claude/Codex indicator per account, install jacked's skills/commands into Codex as well as Claude Code, and switch Codex accounts. This doc is the build's grounding; the matching /goal brief references it.

1. Feasibility verdict (the honest version)

AskVerdictMechanism (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/readprimary=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.

2. How jacked does it for Claude today (the seams to mirror)

Every facet has a single clean chokepoint. The build extends each behind a provider dimension rather than forking parallel code.

SubsystemSingle chokepoint (file:line)What it does
Account modeljacked/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 fetchjacked/web/auth.py:733 fetch_usage(); the provider-specific leaf is :810-832GET 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 / switchjacked/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 launchjacked/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.
Installerjacked/cli.py:2973-3257 _run_install() + install_manifest.py:38-44 CATEGORIESCopies 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 tagsdashboard data/web/js/components/accounts.js:247-348; panel panel.js:89-129; pill service/menubar_mac.py:284-308Cards/rows render from GET /api/auth/accountsAccountResponse (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).

3. Codex target — concrete file shapes & endpoints

3.1 Auth & credentials — ~/.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"
}

3.2 Usage — 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.

3.3 Installer targets (live paths on this machine)

jacked artifactCodex targetNotes
skill data/skills/<n>/SKILL.md~/.agents/skills/<n>/SKILL.md live, populatedSame 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.mdBlock-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>.tomlFormat conversion MD→TOML (name/description/developer_instructions). Lower priority.
MCP[mcp_servers.*] in config.toml / codex mcp addOptional; later.

3.4 Switching — guardrails are mandatory

4. Build plan (milestones — mirror the /goal brief)

  1. Provider-aware model + migration. 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.
  2. Codex credential/identity module. Read ~/.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.
  3. Add a Codex account. 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.
  4. Codex usage fetcher behind provider dispatch. Refactor the fetch_usage leaf (auth.py:810-832) to dispatch on account.provider; Codex impl drives codex app-serveraccount/rateLimits/read → normalized five_hour/seven_day; Anthropic path unchanged (regression test green).
  5. Provider indicator across 3 surfaces. Surface 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.
  6. Codex switching (guardrailed). Per-account CODEX_HOME launch (jacked codex <id>) + in-place active-swap with write-back, field-complete write, restart signal, single-writer guard, stale-snapshot refusal.
  7. Installer Codex target. Detect Codex (binary / ~/.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.

5. Risks & decisions

Decided
Watch

6. Sources

Codex auth/config/usage/skills/hooks verified against openai/codex main and developers.openai.com (mid-2026); switching behavior from community tools + issues.

Generated 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.