Uplink runs every text through Claude Code. Codex should be a supported alternative, chosen per installation.
The seam already exists, so this is an implementation rather than a refactor
Engineis a one-method interface:run(request: EngineRequest) => EngineRun(app/Uplink/engine.ts#L46).ClaudeEngine implements Engine(engine.ts#L61) and is the only implementation.- The orchestrator is already engine-agnostic:
app/Uplink/uplink.tsimports onlyEngineandEngineRun(#L3), holds oneengine: Engine(#L39) and callsengine.run(...)once (#L305).
So a CodexEngine implements Engine drops in, provided it produces the same two shapes: the EngineEvent stream (session, tool, text) and an EngineResult with ok, text, sessionId, costCents, durationMs, turns and authFailure.
What has to change beyond the new class
- Both construction sites select an engine.
app/Uplink/desktop-agent.ts#L175andapp/Commands/Uplink.ts#L22each donew ClaudeEngine({...})today. - The health probe hardcodes Claude.
desktop-agent.ts#L134spawns[bin, '-p', 'Reply with exactly: ok', '--model', 'haiku', '--output-format', 'json']. With Codex selected this probes the wrong CLI and a healthy install reports broken. - Settings are Claude-shaped.
app/Uplink/settings.tscarriesmodel: string | null(#L26,#L37) and exports it asUPLINK_MODEL(#L68). An engine field is needed alongside it, and the menubar (app/Uplink/menubar.html) has to offer it. - Auth detection is Claude's wording. The
AUTH_FAILUREregex inengine.tsmatchesplease run /login,credit balanceand similar, andCLAUDE_CODE_OAUTH_TOKENis deleted when empty (engine.ts#L89-L92). Codex needs its own detection, otherwise a signed-out Codex texts back a generic failure instead of telling the person to sign in. uplink:doctor(app/Uplink/doctor.ts) has to check whichever engine is selected.
Session continuity is the real design question
A thread keeps context because EngineRequest.sessionId is passed to claude --resume (engine.ts#L79), and new starts a fresh one. Codex's resume model is not the same. Decide explicitly how a thread maps onto it, because if it silently starts fresh every text, follow-ups break in a way that is invisible until someone relies on one.
Cost
costCents is documented as integer cents at API rates. If Codex does not report a comparable number, report null rather than a wrong one, and let the dashboard show it as unknown.
Acceptance
- With Codex selected: text a question, get the "Working on it" acknowledgement, then the answer.
status,stop,new,more,pingandhelpbehave identically on both engines.- A follow-up text continues the same conversation, and
newstarts a fresh one. - A signed-out Codex produces a reply that says so, not a generic failure.
uplink:doctorpasses on a working Codex install and fails with the exact fix on a broken one.